From 572dfcd160299489e66454de89a608da6f6d468e Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 12 Dec 2020 08:49:58 -0300 Subject: [PATCH 0001/2772] Compare also paths on Windows when considering ImportPathMismatchError On Windows, os.path.samefile returns false for paths mounted in UNC paths which point to the same location. I couldn't reproduce the actual case reported, but looking at the code it seems this commit should fix the issue. Fix #7678 Fix #8076 --- changelog/7678.bugfix.rst | 2 ++ src/_pytest/pathlib.py | 16 +++++++++++++++- testing/test_pathlib.py | 21 +++++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 changelog/7678.bugfix.rst diff --git a/changelog/7678.bugfix.rst b/changelog/7678.bugfix.rst new file mode 100644 index 00000000000..4adc6ffd119 --- /dev/null +++ b/changelog/7678.bugfix.rst @@ -0,0 +1,2 @@ +Fixed bug where ``ImportPathMismatchError`` would be raised for files compiled in +the host and loaded later from an UNC mounted path (Windows). diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index 6a36ae17ab2..8875a28f84b 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -543,7 +543,7 @@ def import_path( module_file = module_file[: -(len(os.path.sep + "__init__.py"))] try: - is_same = os.path.samefile(str(path), module_file) + is_same = _is_same(str(path), module_file) except FileNotFoundError: is_same = False @@ -553,6 +553,20 @@ def import_path( return mod +# Implement a special _is_same function on Windows which returns True if the two filenames +# compare equal, to circumvent os.path.samefile returning False for mounts in UNC (#7678). +if sys.platform.startswith("win"): + + def _is_same(f1: str, f2: str) -> bool: + return Path(f1) == Path(f2) or os.path.samefile(f1, f2) + + +else: + + def _is_same(f1: str, f2: str) -> bool: + return os.path.samefile(f1, f2) + + def resolve_package_path(path: Path) -> Optional[Path]: """Return the Python package path by looking for the last directory upwards which still contains an __init__.py. diff --git a/testing/test_pathlib.py b/testing/test_pathlib.py index 0507e3d6866..f60b9f26369 100644 --- a/testing/test_pathlib.py +++ b/testing/test_pathlib.py @@ -7,6 +7,7 @@ import py import pytest +from _pytest.monkeypatch import MonkeyPatch from _pytest.pathlib import bestrelpath from _pytest.pathlib import commonpath from _pytest.pathlib import ensure_deletable @@ -414,3 +415,23 @@ def test_visit_ignores_errors(tmpdir) -> None: "bar", "foo", ] + + +@pytest.mark.skipif(not sys.platform.startswith("win"), reason="Windows only") +def test_samefile_false_negatives(tmp_path: Path, monkeypatch: MonkeyPatch) -> None: + """ + import_file() should not raise ImportPathMismatchError if the paths are exactly + equal on Windows. It seems directories mounted as UNC paths make os.path.samefile + return False, even when they are clearly equal. + """ + module_path = tmp_path.joinpath("my_module.py") + module_path.write_text("def foo(): return 42") + monkeypatch.syspath_prepend(tmp_path) + + with monkeypatch.context() as mp: + # Forcibly make os.path.samefile() return False here to ensure we are comparing + # the paths too. Using a context to narrow the patch as much as possible given + # this is an important system function. + mp.setattr(os.path, "samefile", lambda x, y: False) + module = import_path(module_path) + assert getattr(module, "foo")() == 42 From ed658d682961a602305112bee72aa1e8843479f2 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 11 Dec 2020 13:40:37 +0200 Subject: [PATCH 0002/2772] Some py.path.local -> pathlib.Path - Some conftest related functions - _confcutdir - Allow arbitrary os.PathLike[str] in gethookproxy. --- src/_pytest/config/__init__.py | 70 +++++++++++---------- src/_pytest/doctest.py | 3 +- src/_pytest/main.py | 18 +++--- src/_pytest/nodes.py | 2 +- src/_pytest/python.py | 2 +- testing/python/fixtures.py | 12 ++-- testing/test_collection.py | 4 +- testing/test_config.py | 18 +++--- testing/test_conftest.py | 109 +++++++++++++++++---------------- testing/test_pluginmanager.py | 29 +++++---- 10 files changed, 141 insertions(+), 126 deletions(-) diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index bd9e2883f9f..0df4ffa01c1 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -50,9 +50,11 @@ from _pytest.compat import importlib_metadata from _pytest.outcomes import fail from _pytest.outcomes import Skipped +from _pytest.pathlib import absolutepath from _pytest.pathlib import bestrelpath from _pytest.pathlib import import_path from _pytest.pathlib import ImportMode +from _pytest.pathlib import resolve_package_path from _pytest.store import Store from _pytest.warning_types import PytestConfigWarning @@ -102,9 +104,7 @@ class ExitCode(enum.IntEnum): class ConftestImportFailure(Exception): def __init__( - self, - path: py.path.local, - excinfo: Tuple[Type[Exception], Exception, TracebackType], + self, path: Path, excinfo: Tuple[Type[Exception], Exception, TracebackType], ) -> None: super().__init__(path, excinfo) self.path = path @@ -342,9 +342,9 @@ def __init__(self) -> None: self._conftest_plugins: Set[types.ModuleType] = set() # State related to local conftest plugins. - self._dirpath2confmods: Dict[py.path.local, List[types.ModuleType]] = {} + self._dirpath2confmods: Dict[Path, List[types.ModuleType]] = {} self._conftestpath2mod: Dict[Path, types.ModuleType] = {} - self._confcutdir: Optional[py.path.local] = None + self._confcutdir: Optional[Path] = None self._noconftest = False self._duplicatepaths: Set[py.path.local] = set() @@ -479,9 +479,9 @@ def _set_initial_conftests(self, namespace: argparse.Namespace) -> None: All builtin and 3rd party plugins will have been loaded, however, so common options will not confuse our logic here. """ - current = py.path.local() + current = Path.cwd() self._confcutdir = ( - current.join(namespace.confcutdir, abs=True) + absolutepath(current / namespace.confcutdir) if namespace.confcutdir else None ) @@ -495,7 +495,7 @@ def _set_initial_conftests(self, namespace: argparse.Namespace) -> None: i = path.find("::") if i != -1: path = path[:i] - anchor = current.join(path, abs=1) + anchor = absolutepath(current / path) if anchor.exists(): # we found some file object self._try_load_conftest(anchor, namespace.importmode) foundanchor = True @@ -503,24 +503,24 @@ def _set_initial_conftests(self, namespace: argparse.Namespace) -> None: self._try_load_conftest(current, namespace.importmode) def _try_load_conftest( - self, anchor: py.path.local, importmode: Union[str, ImportMode] + self, anchor: Path, importmode: Union[str, ImportMode] ) -> None: self._getconftestmodules(anchor, importmode) # let's also consider test* subdirs - if anchor.check(dir=1): - for x in anchor.listdir("test*"): - if x.check(dir=1): + if anchor.is_dir(): + for x in anchor.glob("test*"): + if x.is_dir(): self._getconftestmodules(x, importmode) @lru_cache(maxsize=128) def _getconftestmodules( - self, path: py.path.local, importmode: Union[str, ImportMode], + self, path: Path, importmode: Union[str, ImportMode], ) -> List[types.ModuleType]: if self._noconftest: return [] - if path.isfile(): - directory = path.dirpath() + if path.is_file(): + directory = path.parent else: directory = path @@ -528,18 +528,18 @@ def _getconftestmodules( # and allow users to opt into looking into the rootdir parent # directories instead of requiring to specify confcutdir. clist = [] - for parent in directory.parts(): - if self._confcutdir and self._confcutdir.relto(parent): + for parent in reversed((directory, *directory.parents)): + if self._confcutdir and parent in self._confcutdir.parents: continue - conftestpath = parent.join("conftest.py") - if conftestpath.isfile(): + conftestpath = parent / "conftest.py" + if conftestpath.is_file(): mod = self._importconftest(conftestpath, importmode) clist.append(mod) self._dirpath2confmods[directory] = clist return clist def _rget_with_confmod( - self, name: str, path: py.path.local, importmode: Union[str, ImportMode], + self, name: str, path: Path, importmode: Union[str, ImportMode], ) -> Tuple[types.ModuleType, Any]: modules = self._getconftestmodules(path, importmode) for mod in reversed(modules): @@ -550,21 +550,21 @@ def _rget_with_confmod( raise KeyError(name) def _importconftest( - self, conftestpath: py.path.local, importmode: Union[str, ImportMode], + self, conftestpath: Path, importmode: Union[str, ImportMode], ) -> types.ModuleType: # 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() + key = conftestpath.resolve() with contextlib.suppress(KeyError): return self._conftestpath2mod[key] - pkgpath = conftestpath.pypkgpath() + pkgpath = resolve_package_path(conftestpath) if pkgpath is None: - _ensure_removed_sysmodule(conftestpath.purebasename) + _ensure_removed_sysmodule(conftestpath.stem) try: mod = import_path(conftestpath, mode=importmode) @@ -577,10 +577,10 @@ def _importconftest( self._conftest_plugins.add(mod) self._conftestpath2mod[key] = mod - dirpath = conftestpath.dirpath() + dirpath = conftestpath.parent if dirpath in self._dirpath2confmods: for path, mods in self._dirpath2confmods.items(): - if path and path.relto(dirpath) or path == dirpath: + if path and dirpath in path.parents or path == dirpath: assert mod not in mods mods.append(mod) self.trace(f"loading conftestmodule {mod!r}") @@ -588,7 +588,7 @@ def _importconftest( return mod def _check_non_top_pytest_plugins( - self, mod: types.ModuleType, conftestpath: py.path.local, + self, mod: types.ModuleType, conftestpath: Path, ) -> None: if ( hasattr(mod, "pytest_plugins") @@ -1412,21 +1412,23 @@ def _getini(self, name: str): assert type in [None, "string"] return value - def _getconftest_pathlist( - self, name: str, path: py.path.local - ) -> Optional[List[py.path.local]]: + def _getconftest_pathlist(self, name: str, path: Path) -> Optional[List[Path]]: try: mod, relroots = self.pluginmanager._rget_with_confmod( name, path, self.getoption("importmode") ) except KeyError: return None - modpath = py.path.local(mod.__file__).dirpath() - values: List[py.path.local] = [] + modpath = Path(mod.__file__).parent + values: List[Path] = [] for relroot in relroots: - if not isinstance(relroot, py.path.local): + if isinstance(relroot, Path): + pass + elif isinstance(relroot, py.path.local): + relroot = Path(relroot) + else: relroot = relroot.replace("/", os.sep) - relroot = modpath.join(relroot, abs=True) + relroot = absolutepath(modpath / relroot) values.append(relroot) return values diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py index 64e8f0e0eee..d0b6b4c4185 100644 --- a/src/_pytest/doctest.py +++ b/src/_pytest/doctest.py @@ -7,6 +7,7 @@ import types import warnings from contextlib import contextmanager +from pathlib import Path from typing import Any from typing import Callable from typing import Dict @@ -525,7 +526,7 @@ def _find( if self.fspath.basename == "conftest.py": module = self.config.pluginmanager._importconftest( - self.fspath, self.config.getoption("importmode") + Path(self.fspath), self.config.getoption("importmode") ) else: try: diff --git a/src/_pytest/main.py b/src/_pytest/main.py index 41a33d4494c..eab3c9afd27 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -371,22 +371,23 @@ def _in_venv(path: py.path.local) -> bool: def pytest_ignore_collect(path: py.path.local, config: Config) -> Optional[bool]: - ignore_paths = config._getconftest_pathlist("collect_ignore", path=path.dirpath()) + path_ = Path(path) + ignore_paths = config._getconftest_pathlist("collect_ignore", path=path_.parent) ignore_paths = ignore_paths or [] excludeopt = config.getoption("ignore") if excludeopt: - ignore_paths.extend([py.path.local(x) for x in excludeopt]) + ignore_paths.extend(absolutepath(x) for x in excludeopt) - if py.path.local(path) in ignore_paths: + if path_ in ignore_paths: return True ignore_globs = config._getconftest_pathlist( - "collect_ignore_glob", path=path.dirpath() + "collect_ignore_glob", path=path_.parent ) ignore_globs = ignore_globs or [] excludeglobopt = config.getoption("ignore_glob") if excludeglobopt: - ignore_globs.extend([py.path.local(x) for x in excludeglobopt]) + ignore_globs.extend(absolutepath(x) for x in excludeglobopt) if any(fnmatch.fnmatch(str(path), str(glob)) for glob in ignore_globs): return True @@ -512,12 +513,12 @@ def pytest_runtest_logreport( def isinitpath(self, path: py.path.local) -> bool: return path in self._initialpaths - def gethookproxy(self, fspath: py.path.local): + def gethookproxy(self, fspath: "os.PathLike[str]"): # Check if we have the common case of running # hooks with all conftest.py files. pm = self.config.pluginmanager my_conftestmodules = pm._getconftestmodules( - fspath, self.config.getoption("importmode") + Path(fspath), self.config.getoption("importmode") ) remove_mods = pm._conftest_plugins.difference(my_conftestmodules) if remove_mods: @@ -668,8 +669,9 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: # No point in finding packages when collecting doctests. if not self.config.getoption("doctestmodules", False): pm = self.config.pluginmanager + confcutdir = py.path.local(pm._confcutdir) if pm._confcutdir else None for parent in reversed(argpath.parts()): - if pm._confcutdir and pm._confcutdir.relto(parent): + if confcutdir and confcutdir.relto(parent): break if parent.isdir(): diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 27434fb6a67..98bd581b96d 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -520,7 +520,7 @@ def from_parent(cls, parent, *, fspath, **kw): """The public constructor.""" return super().from_parent(parent=parent, fspath=fspath, **kw) - def gethookproxy(self, fspath: py.path.local): + def gethookproxy(self, fspath: "os.PathLike[str]"): warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2) return self.session.gethookproxy(fspath) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index e48e7531c19..407f924a5f1 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -653,7 +653,7 @@ def setup(self) -> None: func = partial(_call_with_optional_argument, teardown_module, self.obj) self.addfinalizer(func) - def gethookproxy(self, fspath: py.path.local): + def gethookproxy(self, fspath: "os.PathLike[str]"): warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2) return self.session.gethookproxy(fspath) diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index 94547dd245c..ac62de608e5 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -8,6 +8,7 @@ from _pytest.config import ExitCode from _pytest.fixtures import FixtureRequest from _pytest.pytester import get_public_names +from _pytest.pytester import Pytester from _pytest.pytester import Testdir @@ -1961,8 +1962,10 @@ def test_result(arg): reprec = testdir.inline_run("-v", "-s") reprec.assertoutcome(passed=4) - def test_class_function_parametrization_finalization(self, testdir): - p = testdir.makeconftest( + def test_class_function_parametrization_finalization( + self, pytester: Pytester + ) -> None: + p = pytester.makeconftest( """ import pytest import pprint @@ -1984,7 +1987,7 @@ def fin(): request.addfinalizer(fin) """ ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -1996,8 +1999,7 @@ def test_2(self): pass """ ) - confcut = f"--confcutdir={testdir.tmpdir}" - reprec = testdir.inline_run("-v", "-s", confcut) + reprec = pytester.inline_run("-v", "-s", "--confcutdir", pytester.path) reprec.assertoutcome(passed=8) config = reprec.getcalls("pytest_unconfigure")[0].config values = config.pluginmanager._getconftestmodules(p, importmode="prepend")[ diff --git a/testing/test_collection.py b/testing/test_collection.py index 1138c2bd6f5..862c1aba8d2 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -364,7 +364,9 @@ def pytest_ignore_collect(path, config): def test_collectignore_exclude_on_option(self, pytester: Pytester) -> None: pytester.makeconftest( """ - collect_ignore = ['hello', 'test_world.py'] + import py + from pathlib import Path + collect_ignore = [py.path.local('hello'), 'test_world.py', Path('bye')] def pytest_addoption(parser): parser.addoption("--XX", action="store_true", default=False) def pytest_configure(config): diff --git a/testing/test_config.py b/testing/test_config.py index b931797d429..eacc9c9ebdd 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -574,16 +574,16 @@ def test_getoption(self, pytester: Pytester) -> None: config.getvalue("x") assert config.getoption("x", 1) == 1 - def test_getconftest_pathlist(self, pytester: Pytester, tmpdir) -> None: - somepath = tmpdir.join("x", "y", "z") - p = tmpdir.join("conftest.py") - p.write("pathlist = ['.', %r]" % str(somepath)) + def test_getconftest_pathlist(self, pytester: Pytester, tmp_path: Path) -> None: + somepath = tmp_path.joinpath("x", "y", "z") + p = tmp_path.joinpath("conftest.py") + p.write_text(f"pathlist = ['.', {str(somepath)!r}]") config = pytester.parseconfigure(p) - assert config._getconftest_pathlist("notexist", path=tmpdir) is None - pl = config._getconftest_pathlist("pathlist", path=tmpdir) or [] + assert config._getconftest_pathlist("notexist", path=tmp_path) is None + pl = config._getconftest_pathlist("pathlist", path=tmp_path) or [] print(pl) assert len(pl) == 2 - assert pl[0] == tmpdir + assert pl[0] == tmp_path assert pl[1] == somepath @pytest.mark.parametrize("maybe_type", ["not passed", "None", '"string"']) @@ -1935,10 +1935,10 @@ def test_pytest_plugins_in_non_top_level_conftest_unsupported_no_false_positives assert msg not in res.stdout.str() -def test_conftest_import_error_repr(tmpdir: py.path.local) -> None: +def test_conftest_import_error_repr(tmp_path: Path) -> None: """`ConftestImportFailure` should use a short error message and readable path to the failed conftest.py file.""" - path = tmpdir.join("foo/conftest.py") + path = tmp_path.joinpath("foo/conftest.py") with pytest.raises( ConftestImportFailure, match=re.escape(f"RuntimeError: some error (from {path})"), diff --git a/testing/test_conftest.py b/testing/test_conftest.py index 638321728d7..36e83191bcd 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -4,6 +4,7 @@ from pathlib import Path from typing import cast from typing import Dict +from typing import Generator from typing import List from typing import Optional @@ -15,7 +16,7 @@ from _pytest.monkeypatch import MonkeyPatch from _pytest.pathlib import symlink_or_skip from _pytest.pytester import Pytester -from _pytest.pytester import Testdir +from _pytest.tmpdir import TempPathFactory def ConftestWithSetinitial(path) -> PytestPluginManager: @@ -25,12 +26,12 @@ def ConftestWithSetinitial(path) -> PytestPluginManager: def conftest_setinitial( - conftest: PytestPluginManager, args, confcutdir: Optional[py.path.local] = None + conftest: PytestPluginManager, args, confcutdir: Optional["os.PathLike[str]"] = None ) -> None: class Namespace: def __init__(self) -> None: self.file_or_dir = args - self.confcutdir = str(confcutdir) + self.confcutdir = os.fspath(confcutdir) if confcutdir is not None else None self.noconftest = False self.pyargs = False self.importmode = "prepend" @@ -42,54 +43,58 @@ def __init__(self) -> None: @pytest.mark.usefixtures("_sys_snapshot") class TestConftestValueAccessGlobal: @pytest.fixture(scope="module", params=["global", "inpackage"]) - def basedir(self, request, tmpdir_factory): - tmpdir = tmpdir_factory.mktemp("basedir", numbered=True) - tmpdir.ensure("adir/conftest.py").write("a=1 ; Directory = 3") - tmpdir.ensure("adir/b/conftest.py").write("b=2 ; a = 1.5") + def basedir( + self, request, tmp_path_factory: TempPathFactory + ) -> Generator[Path, None, None]: + tmpdir = tmp_path_factory.mktemp("basedir", numbered=True) + tmpdir.joinpath("adir/b").mkdir(parents=True) + tmpdir.joinpath("adir/conftest.py").write_text("a=1 ; Directory = 3") + tmpdir.joinpath("adir/b/conftest.py").write_text("b=2 ; a = 1.5") if request.param == "inpackage": - tmpdir.ensure("adir/__init__.py") - tmpdir.ensure("adir/b/__init__.py") + tmpdir.joinpath("adir/__init__.py").touch() + tmpdir.joinpath("adir/b/__init__.py").touch() yield tmpdir - def test_basic_init(self, basedir): + def test_basic_init(self, basedir: Path) -> None: conftest = PytestPluginManager() - p = basedir.join("adir") + p = basedir / "adir" assert conftest._rget_with_confmod("a", p, importmode="prepend")[1] == 1 - def test_immediate_initialiation_and_incremental_are_the_same(self, basedir): + def test_immediate_initialiation_and_incremental_are_the_same( + self, basedir: Path + ) -> None: conftest = PytestPluginManager() assert not len(conftest._dirpath2confmods) conftest._getconftestmodules(basedir, importmode="prepend") snap1 = len(conftest._dirpath2confmods) assert snap1 == 1 - conftest._getconftestmodules(basedir.join("adir"), importmode="prepend") + conftest._getconftestmodules(basedir / "adir", importmode="prepend") assert len(conftest._dirpath2confmods) == snap1 + 1 - conftest._getconftestmodules(basedir.join("b"), importmode="prepend") + conftest._getconftestmodules(basedir / "b", importmode="prepend") assert len(conftest._dirpath2confmods) == snap1 + 2 - def test_value_access_not_existing(self, basedir): + def test_value_access_not_existing(self, basedir: Path) -> None: conftest = ConftestWithSetinitial(basedir) with pytest.raises(KeyError): conftest._rget_with_confmod("a", basedir, importmode="prepend") - def test_value_access_by_path(self, basedir): + def test_value_access_by_path(self, basedir: Path) -> None: conftest = ConftestWithSetinitial(basedir) - adir = basedir.join("adir") + adir = basedir / "adir" assert conftest._rget_with_confmod("a", adir, importmode="prepend")[1] == 1 assert ( - conftest._rget_with_confmod("a", adir.join("b"), importmode="prepend")[1] - == 1.5 + conftest._rget_with_confmod("a", adir / "b", importmode="prepend")[1] == 1.5 ) - def test_value_access_with_confmod(self, basedir): - startdir = basedir.join("adir", "b") - startdir.ensure("xx", dir=True) + def test_value_access_with_confmod(self, basedir: Path) -> None: + startdir = basedir / "adir" / "b" + startdir.joinpath("xx").mkdir() conftest = ConftestWithSetinitial(startdir) mod, value = conftest._rget_with_confmod("a", startdir, importmode="prepend") assert value == 1.5 path = py.path.local(mod.__file__) - assert path.dirpath() == basedir.join("adir", "b") + assert path.dirpath() == basedir / "adir" / "b" assert path.purebasename.startswith("conftest") @@ -102,12 +107,12 @@ def test_conftest_in_nonpkg_with_init(tmp_path: Path, _sys_snapshot) -> None: ConftestWithSetinitial(tmp_path.joinpath("adir-1.0", "b")) -def test_doubledash_considered(testdir: Testdir) -> None: - conf = testdir.mkdir("--option") - conf.join("conftest.py").ensure() +def test_doubledash_considered(pytester: Pytester) -> None: + conf = pytester.mkdir("--option") + conf.joinpath("conftest.py").touch() conftest = PytestPluginManager() - conftest_setinitial(conftest, [conf.basename, conf.basename]) - values = conftest._getconftestmodules(py.path.local(conf), importmode="prepend") + conftest_setinitial(conftest, [conf.name, conf.name]) + values = conftest._getconftestmodules(conf, importmode="prepend") assert len(values) == 1 @@ -127,15 +132,18 @@ def test_conftest_global_import(pytester: Pytester) -> None: pytester.makeconftest("x=3") p = pytester.makepyfile( """ - import py, pytest + from pathlib import Path + import pytest from _pytest.config import PytestPluginManager conf = PytestPluginManager() - mod = conf._importconftest(py.path.local("conftest.py"), importmode="prepend") + mod = conf._importconftest(Path("conftest.py"), importmode="prepend") assert mod.x == 3 import conftest assert conftest is mod, (conftest, mod) - subconf = py.path.local().ensure("sub", "conftest.py") - subconf.write("y=4") + sub = Path("sub") + sub.mkdir() + subconf = sub / "conftest.py" + subconf.write_text("y=4") mod2 = conf._importconftest(subconf, importmode="prepend") assert mod != mod2 assert mod2.y == 4 @@ -147,19 +155,19 @@ def test_conftest_global_import(pytester: Pytester) -> None: assert res.ret == 0 -def test_conftestcutdir(testdir: Testdir) -> None: - conf = testdir.makeconftest("") - p = testdir.mkdir("x") +def test_conftestcutdir(pytester: Pytester) -> None: + conf = pytester.makeconftest("") + p = pytester.mkdir("x") conftest = PytestPluginManager() - conftest_setinitial(conftest, [testdir.tmpdir], confcutdir=p) + conftest_setinitial(conftest, [pytester.path], confcutdir=p) values = conftest._getconftestmodules(p, importmode="prepend") assert len(values) == 0 - values = conftest._getconftestmodules(conf.dirpath(), importmode="prepend") + values = conftest._getconftestmodules(conf.parent, importmode="prepend") assert len(values) == 0 assert Path(conf) not in conftest._conftestpath2mod # but we can still import a conftest directly conftest._importconftest(conf, importmode="prepend") - values = conftest._getconftestmodules(conf.dirpath(), importmode="prepend") + values = conftest._getconftestmodules(conf.parent, importmode="prepend") assert values[0].__file__.startswith(str(conf)) # and all sub paths get updated properly values = conftest._getconftestmodules(p, importmode="prepend") @@ -170,10 +178,8 @@ def test_conftestcutdir(testdir: Testdir) -> None: def test_conftestcutdir_inplace_considered(pytester: Pytester) -> None: conf = pytester.makeconftest("") conftest = PytestPluginManager() - conftest_setinitial(conftest, [conf.parent], confcutdir=py.path.local(conf.parent)) - values = conftest._getconftestmodules( - py.path.local(conf.parent), importmode="prepend" - ) + conftest_setinitial(conftest, [conf.parent], confcutdir=conf.parent) + values = conftest._getconftestmodules(conf.parent, importmode="prepend") assert len(values) == 1 assert values[0].__file__.startswith(str(conf)) @@ -184,7 +190,7 @@ def test_setinitial_conftest_subdirs(pytester: Pytester, name: str) -> None: subconftest = sub.joinpath("conftest.py") subconftest.touch() conftest = PytestPluginManager() - conftest_setinitial(conftest, [sub.parent], confcutdir=py.path.local(pytester.path)) + conftest_setinitial(conftest, [sub.parent], confcutdir=pytester.path) key = subconftest.resolve() if name not in ("whatever", ".dotdir"): assert key in conftest._conftestpath2mod @@ -337,22 +343,19 @@ def pytest_addoption(parser): result.stdout.fnmatch_lines(["*--xyz*"]) -def test_conftest_import_order(testdir: Testdir, monkeypatch: MonkeyPatch) -> None: - ct1 = testdir.makeconftest("") - sub = testdir.mkdir("sub") - ct2 = sub.join("conftest.py") - ct2.write("") +def test_conftest_import_order(pytester: Pytester, monkeypatch: MonkeyPatch) -> None: + ct1 = pytester.makeconftest("") + sub = pytester.mkdir("sub") + ct2 = sub / "conftest.py" + ct2.write_text("") def impct(p, importmode): return p conftest = PytestPluginManager() - conftest._confcutdir = testdir.tmpdir + conftest._confcutdir = pytester.path monkeypatch.setattr(conftest, "_importconftest", impct) - mods = cast( - List[py.path.local], - conftest._getconftestmodules(py.path.local(sub), importmode="prepend"), - ) + mods = cast(List[Path], conftest._getconftestmodules(sub, importmode="prepend")) expected = [ct1, ct2] assert mods == expected diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py index 2099f5ae1e3..89f10a7db64 100644 --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -8,6 +8,7 @@ from _pytest.config import PytestPluginManager from _pytest.config.exceptions import UsageError from _pytest.main import Session +from _pytest.pytester import Pytester @pytest.fixture @@ -16,14 +17,16 @@ def pytestpm() -> PytestPluginManager: class TestPytestPluginInteractions: - def test_addhooks_conftestplugin(self, testdir, _config_for_test): - testdir.makepyfile( + def test_addhooks_conftestplugin( + self, pytester: Pytester, _config_for_test + ) -> None: + pytester.makepyfile( newhooks=""" def pytest_myhook(xyz): "new hook" """ ) - conf = testdir.makeconftest( + conf = pytester.makeconftest( """ import newhooks def pytest_addhooks(pluginmanager): @@ -54,10 +57,10 @@ def pytest_addhooks(pluginmanager): assert res.ret != 0 res.stderr.fnmatch_lines(["*did not find*sys*"]) - def test_do_option_postinitialize(self, testdir): - config = testdir.parseconfigure() + def test_do_option_postinitialize(self, pytester: Pytester) -> None: + config = pytester.parseconfigure() assert not hasattr(config.option, "test123") - p = testdir.makepyfile( + p = pytester.makepyfile( """ def pytest_addoption(parser): parser.addoption('--test123', action="store_true", @@ -120,20 +123,20 @@ def pytest_plugin_registered(self): finally: undo() - def test_hook_proxy(self, testdir): + def test_hook_proxy(self, pytester: Pytester) -> None: """Test the gethookproxy function(#2016)""" - config = testdir.parseconfig() + config = pytester.parseconfig() session = Session.from_config(config) - testdir.makepyfile(**{"tests/conftest.py": "", "tests/subdir/conftest.py": ""}) + pytester.makepyfile(**{"tests/conftest.py": "", "tests/subdir/conftest.py": ""}) - conftest1 = testdir.tmpdir.join("tests/conftest.py") - conftest2 = testdir.tmpdir.join("tests/subdir/conftest.py") + conftest1 = pytester.path.joinpath("tests/conftest.py") + conftest2 = pytester.path.joinpath("tests/subdir/conftest.py") config.pluginmanager._importconftest(conftest1, importmode="prepend") - ihook_a = session.gethookproxy(testdir.tmpdir.join("tests")) + ihook_a = session.gethookproxy(pytester.path / "tests") assert ihook_a is not None config.pluginmanager._importconftest(conftest2, importmode="prepend") - ihook_b = session.gethookproxy(testdir.tmpdir.join("tests")) + ihook_b = session.gethookproxy(pytester.path / "tests") assert ihook_a is not ihook_b def test_hook_with_addoption(self, testdir): From 54a7356a9fb165d7d3e48153ede01cb62f05ecee Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 12 Dec 2020 23:21:28 +0200 Subject: [PATCH 0003/2772] Merge pull request #8130 from pytest-dev/release-6.2.0 Prepare release 6.2.0 (cherry picked from commit c475106f12ed87fe908544ff383c5205638c086d) --- changelog/1265.improvement.rst | 1 - changelog/2044.improvement.rst | 1 - changelog/4824.bugfix.rst | 1 - changelog/5299.feature.rst | 2 - changelog/7425.feature.rst | 5 - changelog/7429.doc.rst | 1 - changelog/7469.deprecation.rst | 18 --- changelog/7469.improvement.rst | 23 ---- changelog/7527.improvement.rst | 1 - changelog/7530.deprecation.rst | 4 - changelog/7615.improvement.rst | 1 - changelog/7695.feature.rst | 19 --- changelog/7701.improvement.rst | 1 - changelog/7710.improvement.rst | 4 - changelog/7758.bugfix.rst | 1 - changelog/7780.doc.rst | 1 - changelog/7802.trivial.rst | 1 - changelog/7808.breaking.rst | 1 - changelog/7872.doc.rst | 1 - changelog/7878.doc.rst | 1 - changelog/7911.bugfix.rst | 1 - changelog/7913.bugfix.rst | 1 - changelog/7938.improvement.rst | 1 - changelog/7951.bugfix.rst | 1 - changelog/7981.bugfix.rst | 1 - changelog/7988.deprecation.rst | 3 - changelog/8006.feature.rst | 8 -- changelog/8014.trivial.rst | 2 - changelog/8016.bugfix.rst | 1 - changelog/8023.improvement.rst | 1 - changelog/8032.improvement.rst | 1 - doc/en/announce/index.rst | 1 + doc/en/announce/release-6.2.0.rst | 76 +++++++++++ doc/en/builtin.rst | 10 ++ doc/en/changelog.rst | 197 ++++++++++++++++++++++++++++ doc/en/example/nonpython.rst | 2 +- doc/en/example/parametrize.rst | 6 +- doc/en/example/pythoncollection.rst | 6 +- doc/en/example/reportingdemo.rst | 2 +- doc/en/fixture.rst | 4 +- doc/en/getting-started.rst | 2 +- doc/en/reference.rst | 7 +- doc/en/tmpdir.rst | 4 +- doc/en/writing_plugins.rst | 8 +- 44 files changed, 302 insertions(+), 132 deletions(-) delete mode 100644 changelog/1265.improvement.rst delete mode 100644 changelog/2044.improvement.rst delete mode 100644 changelog/4824.bugfix.rst delete mode 100644 changelog/5299.feature.rst delete mode 100644 changelog/7425.feature.rst delete mode 100644 changelog/7429.doc.rst delete mode 100644 changelog/7469.deprecation.rst delete mode 100644 changelog/7469.improvement.rst delete mode 100644 changelog/7527.improvement.rst delete mode 100644 changelog/7530.deprecation.rst delete mode 100644 changelog/7615.improvement.rst delete mode 100644 changelog/7695.feature.rst delete mode 100644 changelog/7701.improvement.rst delete mode 100644 changelog/7710.improvement.rst delete mode 100644 changelog/7758.bugfix.rst delete mode 100644 changelog/7780.doc.rst delete mode 100644 changelog/7802.trivial.rst delete mode 100644 changelog/7808.breaking.rst delete mode 100644 changelog/7872.doc.rst delete mode 100644 changelog/7878.doc.rst delete mode 100644 changelog/7911.bugfix.rst delete mode 100644 changelog/7913.bugfix.rst delete mode 100644 changelog/7938.improvement.rst delete mode 100644 changelog/7951.bugfix.rst delete mode 100644 changelog/7981.bugfix.rst delete mode 100644 changelog/7988.deprecation.rst delete mode 100644 changelog/8006.feature.rst delete mode 100644 changelog/8014.trivial.rst delete mode 100644 changelog/8016.bugfix.rst delete mode 100644 changelog/8023.improvement.rst delete mode 100644 changelog/8032.improvement.rst create mode 100644 doc/en/announce/release-6.2.0.rst diff --git a/changelog/1265.improvement.rst b/changelog/1265.improvement.rst deleted file mode 100644 index 4e7d98c0a99..00000000000 --- a/changelog/1265.improvement.rst +++ /dev/null @@ -1 +0,0 @@ -Added an ``__str__`` implementation to the :class:`~pytest.pytester.LineMatcher` class which is returned from ``pytester.run_pytest().stdout`` and similar. It returns the entire output, like the existing ``str()`` method. diff --git a/changelog/2044.improvement.rst b/changelog/2044.improvement.rst deleted file mode 100644 index c9e47c3f604..00000000000 --- a/changelog/2044.improvement.rst +++ /dev/null @@ -1 +0,0 @@ -Verbose mode now shows the reason that a test was skipped in the test's terminal line after the "SKIPPED", "XFAIL" or "XPASS". diff --git a/changelog/4824.bugfix.rst b/changelog/4824.bugfix.rst deleted file mode 100644 index f2e6db7ab0f..00000000000 --- a/changelog/4824.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed quadratic behavior and improved performance of collection of items using autouse fixtures and xunit fixtures. diff --git a/changelog/5299.feature.rst b/changelog/5299.feature.rst deleted file mode 100644 index 7853e1833db..00000000000 --- a/changelog/5299.feature.rst +++ /dev/null @@ -1,2 +0,0 @@ -pytest now warns about unraisable exceptions and unhandled thread exceptions that occur in tests on Python>=3.8. -See :ref:`unraisable` for more information. diff --git a/changelog/7425.feature.rst b/changelog/7425.feature.rst deleted file mode 100644 index 47e6f4dbd30..00000000000 --- a/changelog/7425.feature.rst +++ /dev/null @@ -1,5 +0,0 @@ -New :fixture:`pytester` fixture, which is identical to :fixture:`testdir` but its methods return :class:`pathlib.Path` when appropriate instead of ``py.path.local``. - -This is part of the movement to use :class:`pathlib.Path` objects internally, in order to remove the dependency to ``py`` in the future. - -Internally, the old :class:`Testdir <_pytest.pytester.Testdir>` is now a thin wrapper around :class:`Pytester <_pytest.pytester.Pytester>`, preserving the old interface. diff --git a/changelog/7429.doc.rst b/changelog/7429.doc.rst deleted file mode 100644 index e6376b727b2..00000000000 --- a/changelog/7429.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Add more information and use cases about skipping doctests. diff --git a/changelog/7469.deprecation.rst b/changelog/7469.deprecation.rst deleted file mode 100644 index 67d0b2bba46..00000000000 --- a/changelog/7469.deprecation.rst +++ /dev/null @@ -1,18 +0,0 @@ -Directly constructing/calling the following classes/functions is now deprecated: - -- ``_pytest.cacheprovider.Cache`` -- ``_pytest.cacheprovider.Cache.for_config()`` -- ``_pytest.cacheprovider.Cache.clear_cache()`` -- ``_pytest.cacheprovider.Cache.cache_dir_from_config()`` -- ``_pytest.capture.CaptureFixture`` -- ``_pytest.fixtures.FixtureRequest`` -- ``_pytest.fixtures.SubRequest`` -- ``_pytest.logging.LogCaptureFixture`` -- ``_pytest.pytester.Pytester`` -- ``_pytest.pytester.Testdir`` -- ``_pytest.recwarn.WarningsRecorder`` -- ``_pytest.recwarn.WarningsChecker`` -- ``_pytest.tmpdir.TempPathFactory`` -- ``_pytest.tmpdir.TempdirFactory`` - -These have always been considered private, but now issue a deprecation warning, which may become a hard error in pytest 7.0.0. diff --git a/changelog/7469.improvement.rst b/changelog/7469.improvement.rst deleted file mode 100644 index cbd75f05419..00000000000 --- a/changelog/7469.improvement.rst +++ /dev/null @@ -1,23 +0,0 @@ -It is now possible to construct a :class:`MonkeyPatch` object directly as ``pytest.MonkeyPatch()``, -in cases when the :fixture:`monkeypatch` fixture cannot be used. Previously some users imported it -from the private `_pytest.monkeypatch.MonkeyPatch` namespace. - -The types of builtin pytest fixtures are now exported so they may be used in type annotations of test functions. -The newly-exported types are: - -- ``pytest.FixtureRequest`` for the :fixture:`request` fixture. -- ``pytest.Cache`` for the :fixture:`cache` fixture. -- ``pytest.CaptureFixture[str]`` for the :fixture:`capfd` and :fixture:`capsys` fixtures. -- ``pytest.CaptureFixture[bytes]`` for the :fixture:`capfdbinary` and :fixture:`capsysbinary` fixtures. -- ``pytest.LogCaptureFixture`` for the :fixture:`caplog` fixture. -- ``pytest.Pytester`` for the :fixture:`pytester` fixture. -- ``pytest.Testdir`` for the :fixture:`testdir` fixture. -- ``pytest.TempdirFactory`` for the :fixture:`tmpdir_factory` fixture. -- ``pytest.TempPathFactory`` for the :fixture:`tmp_path_factory` fixture. -- ``pytest.MonkeyPatch`` for the :fixture:`monkeypatch` fixture. -- ``pytest.WarningsRecorder`` for the :fixture:`recwarn` fixture. - -Constructing them is not supported (except for `MonkeyPatch`); they are only meant for use in type annotations. -Doing so will emit a deprecation warning, and may become a hard-error in pytest 7.0. - -Subclassing them is also not supported. This is not currently enforced at runtime, but is detected by type-checkers such as mypy. diff --git a/changelog/7527.improvement.rst b/changelog/7527.improvement.rst deleted file mode 100644 index 3a7e063fe6f..00000000000 --- a/changelog/7527.improvement.rst +++ /dev/null @@ -1 +0,0 @@ -When a comparison between :func:`namedtuple ` instances of the same type fails, pytest now shows the differing field names (possibly nested) instead of their indexes. diff --git a/changelog/7530.deprecation.rst b/changelog/7530.deprecation.rst deleted file mode 100644 index 36a763e51f1..00000000000 --- a/changelog/7530.deprecation.rst +++ /dev/null @@ -1,4 +0,0 @@ -The ``--strict`` command-line option has been deprecated, use ``--strict-markers`` instead. - -We have plans to maybe in the future to reintroduce ``--strict`` and make it an encompassing flag for all strictness -related options (``--strict-markers`` and ``--strict-config`` at the moment, more might be introduced in the future). diff --git a/changelog/7615.improvement.rst b/changelog/7615.improvement.rst deleted file mode 100644 index fcf9a1a9b42..00000000000 --- a/changelog/7615.improvement.rst +++ /dev/null @@ -1 +0,0 @@ -:meth:`Node.warn <_pytest.nodes.Node.warn>` now permits any subclass of :class:`Warning`, not just :class:`PytestWarning `. diff --git a/changelog/7695.feature.rst b/changelog/7695.feature.rst deleted file mode 100644 index ec8632fc82a..00000000000 --- a/changelog/7695.feature.rst +++ /dev/null @@ -1,19 +0,0 @@ -A new hook was added, `pytest_markeval_namespace` which should return a dictionary. -This dictionary will be used to augment the "global" variables available to evaluate skipif/xfail/xpass markers. - -Pseudo example - -``conftest.py``: - -.. code-block:: python - - def pytest_markeval_namespace(): - return {"color": "red"} - -``test_func.py``: - -.. code-block:: python - - @pytest.mark.skipif("color == 'blue'", reason="Color is not red") - def test_func(): - assert False diff --git a/changelog/7701.improvement.rst b/changelog/7701.improvement.rst deleted file mode 100644 index e214be9e3fe..00000000000 --- a/changelog/7701.improvement.rst +++ /dev/null @@ -1 +0,0 @@ -Improved reporting when using ``--collected-only``. It will now show the number of collected tests in the summary stats. diff --git a/changelog/7710.improvement.rst b/changelog/7710.improvement.rst deleted file mode 100644 index 91b703ab60f..00000000000 --- a/changelog/7710.improvement.rst +++ /dev/null @@ -1,4 +0,0 @@ -Use strict equality comparison for non-numeric types in :func:`pytest.approx` instead of -raising :class:`TypeError`. - -This was the undocumented behavior before 3.7, but is now officially a supported feature. diff --git a/changelog/7758.bugfix.rst b/changelog/7758.bugfix.rst deleted file mode 100644 index a3119b46c0d..00000000000 --- a/changelog/7758.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed an issue where some files in packages are getting lost from ``--lf`` even though they contain tests that failed. Regressed in pytest 5.4.0. diff --git a/changelog/7780.doc.rst b/changelog/7780.doc.rst deleted file mode 100644 index 631873b156e..00000000000 --- a/changelog/7780.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Classes which should not be inherited from are now marked ``final class`` in the API reference. diff --git a/changelog/7802.trivial.rst b/changelog/7802.trivial.rst deleted file mode 100644 index 1f8bc2c9dc6..00000000000 --- a/changelog/7802.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -The ``attrs`` dependency requirement is now >=19.2.0 instead of >=17.4.0. diff --git a/changelog/7808.breaking.rst b/changelog/7808.breaking.rst deleted file mode 100644 index 114b6a382cf..00000000000 --- a/changelog/7808.breaking.rst +++ /dev/null @@ -1 +0,0 @@ -pytest now supports python3.6+ only. diff --git a/changelog/7872.doc.rst b/changelog/7872.doc.rst deleted file mode 100644 index 46236acbf2a..00000000000 --- a/changelog/7872.doc.rst +++ /dev/null @@ -1 +0,0 @@ -``_pytest.config.argparsing.Parser.addini()`` accepts explicit ``None`` and ``"string"``. diff --git a/changelog/7878.doc.rst b/changelog/7878.doc.rst deleted file mode 100644 index ff5d00d6c02..00000000000 --- a/changelog/7878.doc.rst +++ /dev/null @@ -1 +0,0 @@ -In pull request section, ask to commit after editing changelog and authors file. diff --git a/changelog/7911.bugfix.rst b/changelog/7911.bugfix.rst deleted file mode 100644 index 1ef783fbabb..00000000000 --- a/changelog/7911.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Directories created by by :fixture:`tmp_path` and :fixture:`tmpdir` are now considered stale after 3 days without modification (previous value was 3 hours) to avoid deleting directories still in use in long running test suites. diff --git a/changelog/7913.bugfix.rst b/changelog/7913.bugfix.rst deleted file mode 100644 index f42e7cb9cbd..00000000000 --- a/changelog/7913.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed a crash or hang in :meth:`pytester.spawn <_pytest.pytester.Pytester.spawn>` when the :mod:`readline` module is involved. diff --git a/changelog/7938.improvement.rst b/changelog/7938.improvement.rst deleted file mode 100644 index ffe612d0da6..00000000000 --- a/changelog/7938.improvement.rst +++ /dev/null @@ -1 +0,0 @@ -New ``--sw-skip`` argument which is a shorthand for ``--stepwise-skip``. diff --git a/changelog/7951.bugfix.rst b/changelog/7951.bugfix.rst deleted file mode 100644 index 56c71db7839..00000000000 --- a/changelog/7951.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed handling of recursive symlinks when collecting tests. diff --git a/changelog/7981.bugfix.rst b/changelog/7981.bugfix.rst deleted file mode 100644 index 0a254b5d49d..00000000000 --- a/changelog/7981.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed symlinked directories not being followed during collection. Regressed in pytest 6.1.0. diff --git a/changelog/7988.deprecation.rst b/changelog/7988.deprecation.rst deleted file mode 100644 index 34f646c9ab4..00000000000 --- a/changelog/7988.deprecation.rst +++ /dev/null @@ -1,3 +0,0 @@ -The ``@pytest.yield_fixture`` decorator/function is now deprecated. Use :func:`pytest.fixture` instead. - -``yield_fixture`` has been an alias for ``fixture`` for a very long time, so can be search/replaced safely. diff --git a/changelog/8006.feature.rst b/changelog/8006.feature.rst deleted file mode 100644 index 0203689ba4b..00000000000 --- a/changelog/8006.feature.rst +++ /dev/null @@ -1,8 +0,0 @@ -It is now possible to construct a :class:`~pytest.MonkeyPatch` object directly as ``pytest.MonkeyPatch()``, -in cases when the :fixture:`monkeypatch` fixture cannot be used. Previously some users imported it -from the private `_pytest.monkeypatch.MonkeyPatch` namespace. - -Additionally, :meth:`MonkeyPatch.context ` is now a classmethod, -and can be used as ``with MonkeyPatch.context() as mp: ...``. This is the recommended way to use -``MonkeyPatch`` directly, since unlike the ``monkeypatch`` fixture, an instance created directly -is not ``undo()``-ed automatically. diff --git a/changelog/8014.trivial.rst b/changelog/8014.trivial.rst deleted file mode 100644 index 3b9fb7bc271..00000000000 --- a/changelog/8014.trivial.rst +++ /dev/null @@ -1,2 +0,0 @@ -`.pyc` files created by pytest's assertion rewriting now conform to the newer PEP-552 format on Python>=3.7. -(These files are internal and only interpreted by pytest itself.) diff --git a/changelog/8016.bugfix.rst b/changelog/8016.bugfix.rst deleted file mode 100644 index 94539af5c97..00000000000 --- a/changelog/8016.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed only one doctest being collected when using ``pytest --doctest-modules path/to/an/__init__.py``. diff --git a/changelog/8023.improvement.rst b/changelog/8023.improvement.rst deleted file mode 100644 index c791dabc72d..00000000000 --- a/changelog/8023.improvement.rst +++ /dev/null @@ -1 +0,0 @@ -Added ``'node_modules'`` to default value for :confval:`norecursedirs`. diff --git a/changelog/8032.improvement.rst b/changelog/8032.improvement.rst deleted file mode 100644 index 76789ea5097..00000000000 --- a/changelog/8032.improvement.rst +++ /dev/null @@ -1 +0,0 @@ -:meth:`doClassCleanups ` (introduced in :mod:`unittest` in Python and 3.8) is now called appropriately. diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index f0e44107b69..003a0a1a9ca 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,7 @@ Release announcements :maxdepth: 2 + release-6.2.0 release-6.1.2 release-6.1.1 release-6.1.0 diff --git a/doc/en/announce/release-6.2.0.rst b/doc/en/announce/release-6.2.0.rst new file mode 100644 index 00000000000..af16b830ddd --- /dev/null +++ b/doc/en/announce/release-6.2.0.rst @@ -0,0 +1,76 @@ +pytest-6.2.0 +======================================= + +The pytest team is proud to announce the 6.2.0 release! + +This release contains new features, improvements, bug fixes, and breaking changes, so users +are encouraged to take a look at the CHANGELOG carefully: + + https://docs.pytest.org/en/stable/changelog.html + +For complete documentation, please visit: + + https://docs.pytest.org/en/stable/ + +As usual, you can upgrade from PyPI via: + + pip install -U pytest + +Thanks to all of the contributors to this release: + +* Adam Johnson +* Albert Villanova del Moral +* Anthony Sottile +* Anton +* Ariel Pillemer +* Bruno Oliveira +* Charles Aracil +* Christine M +* Christine Mecklenborg +* Cserna Zsolt +* Dominic Mortlock +* Emiel van de Laar +* Florian Bruhin +* Garvit Shubham +* Gustavo Camargo +* Hugo Martins +* Hugo van Kemenade +* Jakob van Santen +* Josias Aurel +* Jürgen Gmach +* Karthikeyan Singaravelan +* Katarzyna +* Kyle Altendorf +* Manuel Mariñez +* Matthew Hughes +* Matthias Gabriel +* Max Voitko +* Maximilian Cosmo Sitter +* Mikhail Fesenko +* Nimesh Vashistha +* Pedro Algarvio +* Petter Strandmark +* Prakhar Gurunani +* Prashant Sharma +* Ran Benita +* Ronny Pfannschmidt +* Sanket Duthade +* Shubham Adep +* Simon K +* Tanvi Mehta +* Thomas Grainger +* Tim Hoffmann +* Vasilis Gerakaris +* William Jamir Silva +* Zac Hatfield-Dodds +* crricks +* dependabot[bot] +* duthades +* frankgerhardt +* kwgchi +* mickeypash +* symonk + + +Happy testing, +The pytest Development Team diff --git a/doc/en/builtin.rst b/doc/en/builtin.rst index 1d7fe76e3b7..5c7c8dfe666 100644 --- a/doc/en/builtin.rst +++ b/doc/en/builtin.rst @@ -158,6 +158,11 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a function invocation, created as a sub directory of the base temporary directory. + By default, a new base temporary directory is created each test session, + and old bases are removed after 3 sessions, to aid in debugging. If + ``--basetemp`` is used then it is cleared each session. See :ref:`base + temporary directory`. + The returned object is a `py.path.local`_ path object. .. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html @@ -167,6 +172,11 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a function invocation, created as a sub directory of the base temporary directory. + By default, a new base temporary directory is created each test session, + and old bases are removed after 3 sessions, to aid in debugging. If + ``--basetemp`` is used then it is cleared each session. See :ref:`base + temporary directory`. + The returned object is a :class:`pathlib.Path` object. diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index 3f14921a80a..77340b1bb84 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -28,6 +28,203 @@ with advance notice in the **Deprecations** section of releases. .. towncrier release notes start +pytest 6.2.0 (2020-12-12) +========================= + +Breaking Changes +---------------- + +- `#7808 `_: pytest now supports python3.6+ only. + + + +Deprecations +------------ + +- `#7469 `_: Directly constructing/calling the following classes/functions is now deprecated: + + - ``_pytest.cacheprovider.Cache`` + - ``_pytest.cacheprovider.Cache.for_config()`` + - ``_pytest.cacheprovider.Cache.clear_cache()`` + - ``_pytest.cacheprovider.Cache.cache_dir_from_config()`` + - ``_pytest.capture.CaptureFixture`` + - ``_pytest.fixtures.FixtureRequest`` + - ``_pytest.fixtures.SubRequest`` + - ``_pytest.logging.LogCaptureFixture`` + - ``_pytest.pytester.Pytester`` + - ``_pytest.pytester.Testdir`` + - ``_pytest.recwarn.WarningsRecorder`` + - ``_pytest.recwarn.WarningsChecker`` + - ``_pytest.tmpdir.TempPathFactory`` + - ``_pytest.tmpdir.TempdirFactory`` + + These have always been considered private, but now issue a deprecation warning, which may become a hard error in pytest 7.0.0. + + +- `#7530 `_: The ``--strict`` command-line option has been deprecated, use ``--strict-markers`` instead. + + We have plans to maybe in the future to reintroduce ``--strict`` and make it an encompassing flag for all strictness + related options (``--strict-markers`` and ``--strict-config`` at the moment, more might be introduced in the future). + + +- `#7988 `_: The ``@pytest.yield_fixture`` decorator/function is now deprecated. Use :func:`pytest.fixture` instead. + + ``yield_fixture`` has been an alias for ``fixture`` for a very long time, so can be search/replaced safely. + + + +Features +-------- + +- `#5299 `_: pytest now warns about unraisable exceptions and unhandled thread exceptions that occur in tests on Python>=3.8. + See :ref:`unraisable` for more information. + + +- `#7425 `_: New :fixture:`pytester` fixture, which is identical to :fixture:`testdir` but its methods return :class:`pathlib.Path` when appropriate instead of ``py.path.local``. + + This is part of the movement to use :class:`pathlib.Path` objects internally, in order to remove the dependency to ``py`` in the future. + + Internally, the old :class:`Testdir <_pytest.pytester.Testdir>` is now a thin wrapper around :class:`Pytester <_pytest.pytester.Pytester>`, preserving the old interface. + + +- `#7695 `_: A new hook was added, `pytest_markeval_namespace` which should return a dictionary. + This dictionary will be used to augment the "global" variables available to evaluate skipif/xfail/xpass markers. + + Pseudo example + + ``conftest.py``: + + .. code-block:: python + + def pytest_markeval_namespace(): + return {"color": "red"} + + ``test_func.py``: + + .. code-block:: python + + @pytest.mark.skipif("color == 'blue'", reason="Color is not red") + def test_func(): + assert False + + +- `#8006 `_: It is now possible to construct a :class:`~pytest.MonkeyPatch` object directly as ``pytest.MonkeyPatch()``, + in cases when the :fixture:`monkeypatch` fixture cannot be used. Previously some users imported it + from the private `_pytest.monkeypatch.MonkeyPatch` namespace. + + Additionally, :meth:`MonkeyPatch.context ` is now a classmethod, + and can be used as ``with MonkeyPatch.context() as mp: ...``. This is the recommended way to use + ``MonkeyPatch`` directly, since unlike the ``monkeypatch`` fixture, an instance created directly + is not ``undo()``-ed automatically. + + + +Improvements +------------ + +- `#1265 `_: Added an ``__str__`` implementation to the :class:`~pytest.pytester.LineMatcher` class which is returned from ``pytester.run_pytest().stdout`` and similar. It returns the entire output, like the existing ``str()`` method. + + +- `#2044 `_: Verbose mode now shows the reason that a test was skipped in the test's terminal line after the "SKIPPED", "XFAIL" or "XPASS". + + +- `#7469 `_ The types of builtin pytest fixtures are now exported so they may be used in type annotations of test functions. + The newly-exported types are: + + - ``pytest.FixtureRequest`` for the :fixture:`request` fixture. + - ``pytest.Cache`` for the :fixture:`cache` fixture. + - ``pytest.CaptureFixture[str]`` for the :fixture:`capfd` and :fixture:`capsys` fixtures. + - ``pytest.CaptureFixture[bytes]`` for the :fixture:`capfdbinary` and :fixture:`capsysbinary` fixtures. + - ``pytest.LogCaptureFixture`` for the :fixture:`caplog` fixture. + - ``pytest.Pytester`` for the :fixture:`pytester` fixture. + - ``pytest.Testdir`` for the :fixture:`testdir` fixture. + - ``pytest.TempdirFactory`` for the :fixture:`tmpdir_factory` fixture. + - ``pytest.TempPathFactory`` for the :fixture:`tmp_path_factory` fixture. + - ``pytest.MonkeyPatch`` for the :fixture:`monkeypatch` fixture. + - ``pytest.WarningsRecorder`` for the :fixture:`recwarn` fixture. + + Constructing them is not supported (except for `MonkeyPatch`); they are only meant for use in type annotations. + Doing so will emit a deprecation warning, and may become a hard-error in pytest 7.0. + + Subclassing them is also not supported. This is not currently enforced at runtime, but is detected by type-checkers such as mypy. + + +- `#7527 `_: When a comparison between :func:`namedtuple ` instances of the same type fails, pytest now shows the differing field names (possibly nested) instead of their indexes. + + +- `#7615 `_: :meth:`Node.warn <_pytest.nodes.Node.warn>` now permits any subclass of :class:`Warning`, not just :class:`PytestWarning `. + + +- `#7701 `_: Improved reporting when using ``--collected-only``. It will now show the number of collected tests in the summary stats. + + +- `#7710 `_: Use strict equality comparison for non-numeric types in :func:`pytest.approx` instead of + raising :class:`TypeError`. + + This was the undocumented behavior before 3.7, but is now officially a supported feature. + + +- `#7938 `_: New ``--sw-skip`` argument which is a shorthand for ``--stepwise-skip``. + + +- `#8023 `_: Added ``'node_modules'`` to default value for :confval:`norecursedirs`. + + +- `#8032 `_: :meth:`doClassCleanups ` (introduced in :mod:`unittest` in Python and 3.8) is now called appropriately. + + + +Bug Fixes +--------- + +- `#4824 `_: Fixed quadratic behavior and improved performance of collection of items using autouse fixtures and xunit fixtures. + + +- `#7758 `_: Fixed an issue where some files in packages are getting lost from ``--lf`` even though they contain tests that failed. Regressed in pytest 5.4.0. + + +- `#7911 `_: Directories created by by :fixture:`tmp_path` and :fixture:`tmpdir` are now considered stale after 3 days without modification (previous value was 3 hours) to avoid deleting directories still in use in long running test suites. + + +- `#7913 `_: Fixed a crash or hang in :meth:`pytester.spawn <_pytest.pytester.Pytester.spawn>` when the :mod:`readline` module is involved. + + +- `#7951 `_: Fixed handling of recursive symlinks when collecting tests. + + +- `#7981 `_: Fixed symlinked directories not being followed during collection. Regressed in pytest 6.1.0. + + +- `#8016 `_: Fixed only one doctest being collected when using ``pytest --doctest-modules path/to/an/__init__.py``. + + + +Improved Documentation +---------------------- + +- `#7429 `_: Add more information and use cases about skipping doctests. + + +- `#7780 `_: Classes which should not be inherited from are now marked ``final class`` in the API reference. + + +- `#7872 `_: ``_pytest.config.argparsing.Parser.addini()`` accepts explicit ``None`` and ``"string"``. + + +- `#7878 `_: In pull request section, ask to commit after editing changelog and authors file. + + + +Trivial/Internal Changes +------------------------ + +- `#7802 `_: The ``attrs`` dependency requirement is now >=19.2.0 instead of >=17.4.0. + + +- `#8014 `_: `.pyc` files created by pytest's assertion rewriting now conform to the newer PEP-552 format on Python>=3.7. + (These files are internal and only interpreted by pytest itself.) + + pytest 6.1.2 (2020-10-28) ========================= diff --git a/doc/en/example/nonpython.rst b/doc/en/example/nonpython.rst index 558c56772f1..a3477fe1e1d 100644 --- a/doc/en/example/nonpython.rst +++ b/doc/en/example/nonpython.rst @@ -102,4 +102,4 @@ interesting to just look at the collection tree: - ========================== 2 tests found in 0.12s =========================== + ======================== 2 tests collected in 0.12s ======================== diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index d5a11b45192..6e2f53984ee 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -175,7 +175,7 @@ objects, they are still using the default pytest representation: - ========================== 8 tests found in 0.12s =========================== + ======================== 8 tests collected in 0.12s ======================== In ``test_timedistance_v3``, we used ``pytest.param`` to specify the test IDs together with the actual data, instead of listing them separately. @@ -252,7 +252,7 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia - ========================== 4 tests found in 0.12s =========================== + ======================== 4 tests collected in 0.12s ======================== Note that we told ``metafunc.parametrize()`` that your scenario values should be considered class-scoped. With pytest-2.3 this leads to a @@ -328,7 +328,7 @@ Let's first see how it looks like at collection time: - ========================== 2/2 tests found in 0.12s =========================== + ======================== 2 tests collected in 0.12s ======================== And then when we run the test: diff --git a/doc/en/example/pythoncollection.rst b/doc/en/example/pythoncollection.rst index f7917b790ef..a6ce2e742e5 100644 --- a/doc/en/example/pythoncollection.rst +++ b/doc/en/example/pythoncollection.rst @@ -157,7 +157,7 @@ The test collection would look like this: - ========================== 2 tests found in 0.12s =========================== + ======================== 2 tests collected in 0.12s ======================== You can check for multiple glob patterns by adding a space between the patterns: @@ -220,7 +220,7 @@ You can always peek at the collection tree without running tests like this: - ========================== 3 tests found in 0.12s =========================== + ======================== 3 tests collected in 0.12s ======================== .. _customizing-test-collection: @@ -296,7 +296,7 @@ file will be left out: rootdir: $REGENDOC_TMPDIR, configfile: pytest.ini collected 0 items - ========================== no tests found in 0.12s =========================== + ======================= no tests collected in 0.12s ======================== It's also possible to ignore files based on Unix shell-style wildcards by adding patterns to :globalvar:`collect_ignore_glob`. diff --git a/doc/en/example/reportingdemo.rst b/doc/en/example/reportingdemo.rst index f1b973f3b33..6e7dbe49683 100644 --- a/doc/en/example/reportingdemo.rst +++ b/doc/en/example/reportingdemo.rst @@ -446,7 +446,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: def test_reinterpret_fails_with_print_for_the_fun_of_it(self): items = [1, 2, 3] - print("items is {!r}".format(items)) + print(f"items is {items!r}") > a, b = items.pop() E TypeError: cannot unpack non-iterable int object diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index 22b86ffcafc..963fc32e6b0 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -919,7 +919,7 @@ Running the above tests results in the following test IDs being used: - ========================== 10 tests found in 0.12s =========================== + ======================= 10 tests collected in 0.12s ======================== .. _`fixture-parametrize-marks`: @@ -958,7 +958,7 @@ Running this test will *skip* the invocation of ``data_set`` with value ``2``: test_fixture_marks.py::test_data[0] PASSED [ 33%] test_fixture_marks.py::test_data[1] PASSED [ 66%] - test_fixture_marks.py::test_data[2] SKIPPED [100%] + test_fixture_marks.py::test_data[2] SKIPPED (unconditional skip) [100%] ======================= 2 passed, 1 skipped in 0.12s ======================= diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index 4814b850586..fe15c218cde 100644 --- a/doc/en/getting-started.rst +++ b/doc/en/getting-started.rst @@ -28,7 +28,7 @@ Install ``pytest`` .. code-block:: bash $ pytest --version - pytest 6.1.2 + pytest 6.2.0 .. _`simpletest`: diff --git a/doc/en/reference.rst b/doc/en/reference.rst index f62b51414b4..8aa95ca6448 100644 --- a/doc/en/reference.rst +++ b/doc/en/reference.rst @@ -1749,7 +1749,8 @@ All the command-line flags can be obtained by running ``pytest --help``:: failures. --sw, --stepwise exit on test failure and continue from last failing test next time - --stepwise-skip ignore the first failing test but stop on the next + --sw-skip, --stepwise-skip + ignore the first failing test but stop on the next failing test reporting: @@ -1791,9 +1792,9 @@ All the command-line flags can be obtained by running ``pytest --help``:: --maxfail=num exit after first num failures or errors. --strict-config any warnings encountered while parsing the `pytest` section of the configuration file raise errors. - --strict-markers, --strict - markers not registered in the `markers` section of + --strict-markers markers not registered in the `markers` section of the configuration file raise errors. + --strict (deprecated) alias to --strict-markers. -c file load configuration from `file` instead of trying to locate one of the implicit configuration files. --continue-on-collection-errors diff --git a/doc/en/tmpdir.rst b/doc/en/tmpdir.rst index c8da5877b28..adcba02cb15 100644 --- a/doc/en/tmpdir.rst +++ b/doc/en/tmpdir.rst @@ -61,7 +61,7 @@ Running this would result in a passed test except for the last > assert 0 E assert 0 - test_tmp_path.py:13: AssertionError + test_tmp_path.py:11: AssertionError ========================= short test summary info ========================== FAILED test_tmp_path.py::test_create_file - assert 0 ============================ 1 failed in 0.12s ============================= @@ -129,7 +129,7 @@ Running this would result in a passed test except for the last > assert 0 E assert 0 - test_tmpdir.py:9: AssertionError + test_tmpdir.py:6: AssertionError ========================= short test summary info ========================== FAILED test_tmpdir.py::test_create_file - assert 0 ============================ 1 failed in 0.12s ============================= diff --git a/doc/en/writing_plugins.rst b/doc/en/writing_plugins.rst index 41967525b33..908366d5290 100644 --- a/doc/en/writing_plugins.rst +++ b/doc/en/writing_plugins.rst @@ -449,13 +449,7 @@ Additionally it is possible to copy examples for an example folder before runnin test_example.py .. [100%] - ============================= warnings summary ============================= - test_example.py::test_plugin - $REGENDOC_TMPDIR/test_example.py:4: PytestExperimentalApiWarning: testdir.copy_example is an experimental api that may change over time - testdir.copy_example("test_example.py") - - -- Docs: https://docs.pytest.org/en/stable/warnings.html - ======================= 2 passed, 1 warning in 0.12s ======================= + ============================ 2 passed in 0.12s ============================= For more information about the result object that ``runpytest()`` returns, and the methods that it provides please check out the :py:class:`RunResult From 28588bf535b71680df7dbe6bbb936148f06a94ac Mon Sep 17 00:00:00 2001 From: bot2x Date: Sun, 6 Dec 2020 14:50:33 +0530 Subject: [PATCH 0004/2772] migrates test_warnings.py from testdir to pytester --- testing/test_warnings.py | 224 ++++++++++++++++++++------------------- 1 file changed, 114 insertions(+), 110 deletions(-) diff --git a/testing/test_warnings.py b/testing/test_warnings.py index 66898041f08..574f3f1ec02 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -6,18 +6,18 @@ import pytest from _pytest.fixtures import FixtureRequest -from _pytest.pytester import Testdir +from _pytest.pytester import Pytester WARNINGS_SUMMARY_HEADER = "warnings summary" @pytest.fixture -def pyfile_with_warnings(testdir: Testdir, request: FixtureRequest) -> str: +def pyfile_with_warnings(pytester: Pytester, request: FixtureRequest) -> str: """Create a test file which calls a function in a module which generates warnings.""" - testdir.syspathinsert() + pytester.syspathinsert() test_name = request.function.__name__ module_name = test_name.lstrip("test_") + "_module" - test_file = testdir.makepyfile( + test_file = pytester.makepyfile( """ import {module_name} def test_func(): @@ -39,9 +39,9 @@ def foo(): @pytest.mark.filterwarnings("default") -def test_normal_flow(testdir, pyfile_with_warnings): +def test_normal_flow(pytester: Pytester, pyfile_with_warnings) -> None: """Check that the warnings section is displayed.""" - result = testdir.runpytest(pyfile_with_warnings) + result = pytester.runpytest(pyfile_with_warnings) result.stdout.fnmatch_lines( [ "*== %s ==*" % WARNINGS_SUMMARY_HEADER, @@ -56,8 +56,8 @@ def test_normal_flow(testdir, pyfile_with_warnings): @pytest.mark.filterwarnings("always") -def test_setup_teardown_warnings(testdir): - testdir.makepyfile( +def test_setup_teardown_warnings(pytester: Pytester) -> None: + pytester.makepyfile( """ import warnings import pytest @@ -72,7 +72,7 @@ def test_func(fix): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( [ "*== %s ==*" % WARNINGS_SUMMARY_HEADER, @@ -86,10 +86,10 @@ def test_func(fix): @pytest.mark.parametrize("method", ["cmdline", "ini"]) -def test_as_errors(testdir, pyfile_with_warnings, method): +def test_as_errors(pytester: Pytester, pyfile_with_warnings, method) -> None: args = ("-W", "error") if method == "cmdline" else () if method == "ini": - testdir.makeini( + pytester.makeini( """ [pytest] filterwarnings=error @@ -97,7 +97,7 @@ def test_as_errors(testdir, pyfile_with_warnings, method): ) # Use a subprocess, since changing logging level affects other threads # (xdist). - result = testdir.runpytest_subprocess(*args, pyfile_with_warnings) + result = pytester.runpytest_subprocess(*args, pyfile_with_warnings) result.stdout.fnmatch_lines( [ "E UserWarning: user warning", @@ -108,24 +108,24 @@ def test_as_errors(testdir, pyfile_with_warnings, method): @pytest.mark.parametrize("method", ["cmdline", "ini"]) -def test_ignore(testdir, pyfile_with_warnings, method): +def test_ignore(pytester: Pytester, pyfile_with_warnings, method) -> None: args = ("-W", "ignore") if method == "cmdline" else () if method == "ini": - testdir.makeini( + pytester.makeini( """ [pytest] filterwarnings= ignore """ ) - result = testdir.runpytest(*args, pyfile_with_warnings) + result = pytester.runpytest(*args, pyfile_with_warnings) result.stdout.fnmatch_lines(["* 1 passed in *"]) assert WARNINGS_SUMMARY_HEADER not in result.stdout.str() @pytest.mark.filterwarnings("always") -def test_unicode(testdir): - testdir.makepyfile( +def test_unicode(pytester: Pytester) -> None: + pytester.makepyfile( """ import warnings import pytest @@ -140,7 +140,7 @@ def test_func(fix): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( [ "*== %s ==*" % WARNINGS_SUMMARY_HEADER, @@ -150,9 +150,9 @@ def test_func(fix): ) -def test_works_with_filterwarnings(testdir): +def test_works_with_filterwarnings(pytester: Pytester) -> None: """Ensure our warnings capture does not mess with pre-installed filters (#2430).""" - testdir.makepyfile( + pytester.makepyfile( """ import warnings @@ -170,22 +170,22 @@ def test_my_warning(self): assert True """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*== 1 passed in *"]) @pytest.mark.parametrize("default_config", ["ini", "cmdline"]) -def test_filterwarnings_mark(testdir, default_config): +def test_filterwarnings_mark(pytester: Pytester, default_config) -> None: """Test ``filterwarnings`` mark works and takes precedence over command line and ini options.""" if default_config == "ini": - testdir.makeini( + pytester.makeini( """ [pytest] filterwarnings = always """ ) - testdir.makepyfile( + pytester.makepyfile( """ import warnings import pytest @@ -202,13 +202,13 @@ def test_show_warning(): warnings.warn(RuntimeWarning()) """ ) - result = testdir.runpytest("-W always" if default_config == "cmdline" else "") + result = pytester.runpytest("-W always" if default_config == "cmdline" else "") result.stdout.fnmatch_lines(["*= 1 failed, 2 passed, 1 warning in *"]) -def test_non_string_warning_argument(testdir): +def test_non_string_warning_argument(pytester: Pytester) -> None: """Non-str argument passed to warning breaks pytest (#2956)""" - testdir.makepyfile( + pytester.makepyfile( """\ import warnings import pytest @@ -217,13 +217,13 @@ def test(): warnings.warn(UserWarning(1, 'foo')) """ ) - result = testdir.runpytest("-W", "always") + result = pytester.runpytest("-W", "always") result.stdout.fnmatch_lines(["*= 1 passed, 1 warning in *"]) -def test_filterwarnings_mark_registration(testdir): +def test_filterwarnings_mark_registration(pytester: Pytester) -> None: """Ensure filterwarnings mark is registered""" - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -232,19 +232,19 @@ def test_func(): pass """ ) - result = testdir.runpytest("--strict-markers") + result = pytester.runpytest("--strict-markers") assert result.ret == 0 @pytest.mark.filterwarnings("always") -def test_warning_captured_hook(testdir): - testdir.makeconftest( +def test_warning_captured_hook(pytester: Pytester) -> None: + pytester.makeconftest( """ def pytest_configure(config): config.issue_config_time_warning(UserWarning("config warning"), stacklevel=2) """ ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest, warnings @@ -268,7 +268,7 @@ class WarningCollector: def pytest_warning_recorded(self, warning_message, when, nodeid, location): collected.append((str(warning_message.message), when, nodeid, location)) - result = testdir.runpytest(plugins=[WarningCollector()]) + result = pytester.runpytest(plugins=[WarningCollector()]) result.stdout.fnmatch_lines(["*1 passed*"]) expected = [ @@ -298,9 +298,9 @@ def pytest_warning_recorded(self, warning_message, when, nodeid, location): @pytest.mark.filterwarnings("always") -def test_collection_warnings(testdir): +def test_collection_warnings(pytester: Pytester) -> None: """Check that we also capture warnings issued during test collection (#3251).""" - testdir.makepyfile( + pytester.makepyfile( """ import warnings @@ -310,7 +310,7 @@ def test_foo(): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( [ "*== %s ==*" % WARNINGS_SUMMARY_HEADER, @@ -322,9 +322,9 @@ def test_foo(): @pytest.mark.filterwarnings("always") -def test_mark_regex_escape(testdir): +def test_mark_regex_escape(pytester: Pytester) -> None: """@pytest.mark.filterwarnings should not try to escape regex characters (#3936)""" - testdir.makepyfile( + pytester.makepyfile( r""" import pytest, warnings @@ -333,15 +333,17 @@ def test_foo(): warnings.warn(UserWarning("some (warning)")) """ ) - result = testdir.runpytest() + result = pytester.runpytest() assert WARNINGS_SUMMARY_HEADER not in result.stdout.str() @pytest.mark.filterwarnings("default") @pytest.mark.parametrize("ignore_pytest_warnings", ["no", "ini", "cmdline"]) -def test_hide_pytest_internal_warnings(testdir, ignore_pytest_warnings): +def test_hide_pytest_internal_warnings( + pytester: Pytester, ignore_pytest_warnings +) -> None: """Make sure we can ignore internal pytest warnings using a warnings filter.""" - testdir.makepyfile( + pytester.makepyfile( """ import pytest import warnings @@ -353,7 +355,7 @@ def test_bar(): """ ) if ignore_pytest_warnings == "ini": - testdir.makeini( + pytester.makeini( """ [pytest] filterwarnings = ignore::pytest.PytestWarning @@ -364,7 +366,7 @@ def test_bar(): if ignore_pytest_warnings == "cmdline" else [] ) - result = testdir.runpytest(*args) + result = pytester.runpytest(*args) if ignore_pytest_warnings != "no": assert WARNINGS_SUMMARY_HEADER not in result.stdout.str() else: @@ -378,15 +380,17 @@ def test_bar(): @pytest.mark.parametrize("ignore_on_cmdline", [True, False]) -def test_option_precedence_cmdline_over_ini(testdir, ignore_on_cmdline): +def test_option_precedence_cmdline_over_ini( + pytester: Pytester, ignore_on_cmdline +) -> None: """Filters defined in the command-line should take precedence over filters in ini files (#3946).""" - testdir.makeini( + pytester.makeini( """ [pytest] filterwarnings = error """ ) - testdir.makepyfile( + pytester.makepyfile( """ import warnings def test(): @@ -394,22 +398,22 @@ def test(): """ ) args = ["-W", "ignore"] if ignore_on_cmdline else [] - result = testdir.runpytest(*args) + result = pytester.runpytest(*args) if ignore_on_cmdline: result.stdout.fnmatch_lines(["* 1 passed in*"]) else: result.stdout.fnmatch_lines(["* 1 failed in*"]) -def test_option_precedence_mark(testdir): +def test_option_precedence_mark(pytester: Pytester) -> None: """Filters defined by marks should always take precedence (#3946).""" - testdir.makeini( + pytester.makeini( """ [pytest] filterwarnings = ignore """ ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest, warnings @pytest.mark.filterwarnings('error') @@ -417,7 +421,7 @@ def test(): warnings.warn(UserWarning('hello')) """ ) - result = testdir.runpytest("-W", "ignore") + result = pytester.runpytest("-W", "ignore") result.stdout.fnmatch_lines(["* 1 failed in*"]) @@ -427,8 +431,8 @@ class TestDeprecationWarningsByDefault: from pytest's own test suite """ - def create_file(self, testdir, mark=""): - testdir.makepyfile( + def create_file(self, pytester: Pytester, mark="") -> None: + pytester.makepyfile( """ import pytest, warnings @@ -443,18 +447,18 @@ def test_foo(): ) @pytest.mark.parametrize("customize_filters", [True, False]) - def test_shown_by_default(self, testdir, customize_filters): + def test_shown_by_default(self, pytester: Pytester, customize_filters) -> None: """Show deprecation warnings by default, even if user has customized the warnings filters (#4013).""" - self.create_file(testdir) + self.create_file(pytester) if customize_filters: - testdir.makeini( + pytester.makeini( """ [pytest] filterwarnings = once::UserWarning """ ) - result = testdir.runpytest_subprocess() + result = pytester.runpytest_subprocess() result.stdout.fnmatch_lines( [ "*== %s ==*" % WARNINGS_SUMMARY_HEADER, @@ -464,9 +468,9 @@ def test_shown_by_default(self, testdir, customize_filters): ] ) - def test_hidden_by_ini(self, testdir): - self.create_file(testdir) - testdir.makeini( + def test_hidden_by_ini(self, pytester: Pytester) -> None: + self.create_file(pytester) + pytester.makeini( """ [pytest] filterwarnings = @@ -474,18 +478,18 @@ def test_hidden_by_ini(self, testdir): ignore::PendingDeprecationWarning """ ) - result = testdir.runpytest_subprocess() + result = pytester.runpytest_subprocess() assert WARNINGS_SUMMARY_HEADER not in result.stdout.str() - def test_hidden_by_mark(self, testdir): + def test_hidden_by_mark(self, pytester: Pytester) -> None: """Should hide the deprecation warning from the function, but the warning during collection should be displayed normally. """ self.create_file( - testdir, + pytester, mark='@pytest.mark.filterwarnings("ignore::PendingDeprecationWarning")', ) - result = testdir.runpytest_subprocess() + result = pytester.runpytest_subprocess() result.stdout.fnmatch_lines( [ "*== %s ==*" % WARNINGS_SUMMARY_HEADER, @@ -494,9 +498,9 @@ def test_hidden_by_mark(self, testdir): ] ) - def test_hidden_by_cmdline(self, testdir): - self.create_file(testdir) - result = testdir.runpytest_subprocess( + def test_hidden_by_cmdline(self, pytester: Pytester) -> None: + self.create_file(pytester) + result = pytester.runpytest_subprocess( "-W", "ignore::DeprecationWarning", "-W", @@ -504,10 +508,10 @@ def test_hidden_by_cmdline(self, testdir): ) assert WARNINGS_SUMMARY_HEADER not in result.stdout.str() - def test_hidden_by_system(self, testdir, monkeypatch): - self.create_file(testdir) + def test_hidden_by_system(self, pytester: Pytester, monkeypatch) -> None: + self.create_file(pytester) monkeypatch.setenv("PYTHONWARNINGS", "once::UserWarning") - result = testdir.runpytest_subprocess() + result = pytester.runpytest_subprocess() assert WARNINGS_SUMMARY_HEADER not in result.stdout.str() @@ -515,13 +519,13 @@ def test_hidden_by_system(self, testdir, monkeypatch): @pytest.mark.skip( reason="This test should be enabled again before pytest 7.0 is released" ) -def test_deprecation_warning_as_error(testdir, change_default): +def test_deprecation_warning_as_error(pytester: Pytester, change_default) -> None: """This ensures that PytestDeprecationWarnings raised by pytest are turned into errors. This test should be enabled as part of each major release, and skipped again afterwards to ensure our deprecations are turning into warnings as expected. """ - testdir.makepyfile( + pytester.makepyfile( """ import warnings, pytest def test(): @@ -529,7 +533,7 @@ def test(): """ ) if change_default == "ini": - testdir.makeini( + pytester.makeini( """ [pytest] filterwarnings = @@ -542,7 +546,7 @@ def test(): if change_default == "cmdline" else () ) - result = testdir.runpytest(*args) + result = pytester.runpytest(*args) if change_default is None: result.stdout.fnmatch_lines(["* 1 failed in *"]) else: @@ -552,23 +556,23 @@ def test(): class TestAssertionWarnings: @staticmethod - def assert_result_warns(result, msg): + def assert_result_warns(result, msg) -> None: result.stdout.fnmatch_lines(["*PytestAssertRewriteWarning: %s*" % msg]) - def test_tuple_warning(self, testdir): - testdir.makepyfile( + def test_tuple_warning(self, pytester: Pytester) -> None: + pytester.makepyfile( """\ def test_foo(): assert (1,2) """ ) - result = testdir.runpytest() + result = pytester.runpytest() self.assert_result_warns( result, "assertion is always true, perhaps remove parentheses?" ) -def test_warnings_checker_twice(): +def test_warnings_checker_twice() -> None: """Issue #4617""" expectation = pytest.warns(UserWarning) with expectation: @@ -579,9 +583,9 @@ def test_warnings_checker_twice(): @pytest.mark.filterwarnings("ignore::pytest.PytestExperimentalApiWarning") @pytest.mark.filterwarnings("always") -def test_group_warnings_by_message(testdir): - testdir.copy_example("warnings/test_group_warnings_by_message.py") - result = testdir.runpytest() +def test_group_warnings_by_message(pytester: Pytester) -> None: + pytester.copy_example("warnings/test_group_warnings_by_message.py") + result = pytester.runpytest() result.stdout.fnmatch_lines( [ "*== %s ==*" % WARNINGS_SUMMARY_HEADER, @@ -611,10 +615,10 @@ def test_group_warnings_by_message(testdir): @pytest.mark.filterwarnings("ignore::pytest.PytestExperimentalApiWarning") @pytest.mark.filterwarnings("always") -def test_group_warnings_by_message_summary(testdir): - testdir.copy_example("warnings/test_group_warnings_by_message_summary") - testdir.syspathinsert() - result = testdir.runpytest() +def test_group_warnings_by_message_summary(pytester: Pytester) -> None: + pytester.copy_example("warnings/test_group_warnings_by_message_summary") + pytester.syspathinsert() + result = pytester.runpytest() result.stdout.fnmatch_lines( [ "*== %s ==*" % WARNINGS_SUMMARY_HEADER, @@ -634,9 +638,9 @@ def test_group_warnings_by_message_summary(testdir): ) -def test_pytest_configure_warning(testdir, recwarn): +def test_pytest_configure_warning(pytester: Pytester, recwarn) -> None: """Issue 5115.""" - testdir.makeconftest( + pytester.makeconftest( """ def pytest_configure(): import warnings @@ -645,7 +649,7 @@ def pytest_configure(): """ ) - result = testdir.runpytest() + result = pytester.runpytest() assert result.ret == 5 assert "INTERNALERROR" not in result.stderr.str() warning = recwarn.pop() @@ -654,7 +658,7 @@ def pytest_configure(): class TestStackLevel: @pytest.fixture - def capwarn(self, testdir): + def capwarn(self, pytester: Pytester): class CapturedWarnings: captured: List[ Tuple[warnings.WarningMessage, Optional[Tuple[str, int, str]]] @@ -664,16 +668,16 @@ class CapturedWarnings: def pytest_warning_recorded(cls, warning_message, when, nodeid, location): cls.captured.append((warning_message, location)) - testdir.plugins = [CapturedWarnings()] + pytester.plugins = [CapturedWarnings()] return CapturedWarnings - def test_issue4445_rewrite(self, testdir, capwarn): + def test_issue4445_rewrite(self, pytester: Pytester, capwarn) -> None: """#4445: Make sure the warning points to a reasonable location See origin of _issue_warning_captured at: _pytest.assertion.rewrite.py:241 """ - testdir.makepyfile(some_mod="") - conftest = testdir.makeconftest( + pytester.makepyfile(some_mod="") + conftest = pytester.makeconftest( """ import some_mod import pytest @@ -681,7 +685,7 @@ def test_issue4445_rewrite(self, testdir, capwarn): pytest.register_assert_rewrite("some_mod") """ ) - testdir.parseconfig() + pytester.parseconfig() # with stacklevel=5 the warning originates from register_assert_rewrite # function in the created conftest.py @@ -694,16 +698,16 @@ def test_issue4445_rewrite(self, testdir, capwarn): assert func == "" # the above conftest.py assert lineno == 4 - def test_issue4445_preparse(self, testdir, capwarn): + def test_issue4445_preparse(self, pytester: Pytester, capwarn) -> None: """#4445: Make sure the warning points to a reasonable location See origin of _issue_warning_captured at: _pytest.config.__init__.py:910 """ - testdir.makeconftest( + pytester.makeconftest( """ import nothing """ ) - testdir.parseconfig("--help") + pytester.parseconfig("--help") # with stacklevel=2 the warning should originate from config._preparse and is # thrown by an errorneous conftest.py @@ -716,29 +720,29 @@ def test_issue4445_preparse(self, testdir, capwarn): assert func == "_preparse" @pytest.mark.filterwarnings("default") - def test_conftest_warning_captured(self, testdir: Testdir) -> None: + def test_conftest_warning_captured(self, pytester: Pytester) -> None: """Warnings raised during importing of conftest.py files is captured (#2891).""" - testdir.makeconftest( + pytester.makeconftest( """ import warnings warnings.warn(UserWarning("my custom warning")) """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( ["conftest.py:2", "*UserWarning: my custom warning*"] ) - def test_issue4445_import_plugin(self, testdir, capwarn): + def test_issue4445_import_plugin(self, pytester: Pytester, capwarn) -> None: """#4445: Make sure the warning points to a reasonable location""" - testdir.makepyfile( + pytester.makepyfile( some_plugin=""" import pytest pytest.skip("thing", allow_module_level=True) """ ) - testdir.syspathinsert() - testdir.parseconfig("-p", "some_plugin") + pytester.syspathinsert() + pytester.parseconfig("-p", "some_plugin") # with stacklevel=2 the warning should originate from # config.PytestPluginManager.import_plugin is thrown by a skipped plugin @@ -751,11 +755,11 @@ def test_issue4445_import_plugin(self, testdir, capwarn): assert f"config{os.sep}__init__.py" in file assert func == "_warn_about_skipped_plugins" - def test_issue4445_issue5928_mark_generator(self, testdir): + def test_issue4445_issue5928_mark_generator(self, pytester: Pytester) -> None: """#4445 and #5928: Make sure the warning from an unknown mark points to the test file where this mark is used. """ - testfile = testdir.makepyfile( + testfile = pytester.makepyfile( """ import pytest @@ -764,7 +768,7 @@ def test_it(): pass """ ) - result = testdir.runpytest_subprocess() + result = pytester.runpytest_subprocess() # with stacklevel=2 the warning should originate from the above created test file result.stdout.fnmatch_lines_random( [ From 4ed9a385192c252cc4d64a0775fa744b8fb95063 Mon Sep 17 00:00:00 2001 From: antonblr Date: Wed, 9 Dec 2020 21:47:58 -0800 Subject: [PATCH 0005/2772] tests: Migrate testing/python to pytester fixture --- src/_pytest/pytester.py | 16 +- testing/python/collect.py | 584 +++++----- testing/python/fixtures.py | 1254 ++++++++++++---------- testing/python/integration.py | 103 +- testing/python/metafunc.py | 362 ++++--- testing/python/raises.py | 19 +- testing/python/show_fixtures_per_test.py | 45 +- 7 files changed, 1251 insertions(+), 1132 deletions(-) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 6833eb02149..0d1f8f278f9 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -1190,7 +1190,9 @@ def parseconfigure(self, *args: Union[str, "os.PathLike[str]"]) -> Config: config._do_configure() return config - def getitem(self, source: str, funcname: str = "test_func") -> Item: + def getitem( + self, source: Union[str, "os.PathLike[str]"], funcname: str = "test_func" + ) -> Item: """Return the test item for a test function. Writes the source to a python file and runs pytest's collection on @@ -1210,7 +1212,7 @@ def getitem(self, source: str, funcname: str = "test_func") -> Item: funcname, source, items ) - def getitems(self, source: str) -> List[Item]: + def getitems(self, source: Union[str, "os.PathLike[str]"]) -> List[Item]: """Return all test items collected from the module. Writes the source to a Python file and runs pytest's collection on @@ -1220,7 +1222,11 @@ def getitems(self, source: str) -> List[Item]: return self.genitems([modcol]) def getmodulecol( - self, source: Union[str, Path], configargs=(), *, withinit: bool = False + self, + source: Union[str, "os.PathLike[str]"], + configargs=(), + *, + withinit: bool = False, ): """Return the module collection node for ``source``. @@ -1238,7 +1244,9 @@ def getmodulecol( Whether to also write an ``__init__.py`` file to the same directory to ensure it is a package. """ - if isinstance(source, Path): + # TODO: Remove type ignore in next mypy release (> 0.790). + # https://github.com/python/typeshed/pull/4582 + if isinstance(source, os.PathLike): # type: ignore[misc] path = self.path.joinpath(source) assert not withinit, "not supported for paths" else: diff --git a/testing/python/collect.py b/testing/python/collect.py index 4d5f4c6895f..c52fb017d0c 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -1,3 +1,4 @@ +import os import sys import textwrap from typing import Any @@ -6,42 +7,50 @@ import _pytest._code import pytest from _pytest.config import ExitCode +from _pytest.monkeypatch import MonkeyPatch from _pytest.nodes import Collector -from _pytest.pytester import Testdir +from _pytest.pytester import Pytester +from _pytest.python import Class +from _pytest.python import Instance class TestModule: - def test_failing_import(self, testdir): - modcol = testdir.getmodulecol("import alksdjalskdjalkjals") + def test_failing_import(self, pytester: Pytester) -> None: + modcol = pytester.getmodulecol("import alksdjalskdjalkjals") pytest.raises(Collector.CollectError, modcol.collect) - def test_import_duplicate(self, testdir): - a = testdir.mkdir("a") - b = testdir.mkdir("b") - p = a.ensure("test_whatever.py") - p.pyimport() - del sys.modules["test_whatever"] - b.ensure("test_whatever.py") - result = testdir.runpytest() + def test_import_duplicate(self, pytester: Pytester) -> None: + a = pytester.mkdir("a") + b = pytester.mkdir("b") + p1 = a.joinpath("test_whatever.py") + p1.touch() + p2 = b.joinpath("test_whatever.py") + p2.touch() + # ensure we don't have it imported already + sys.modules.pop(p1.stem, None) + + result = pytester.runpytest() result.stdout.fnmatch_lines( [ "*import*mismatch*", "*imported*test_whatever*", - "*%s*" % a.join("test_whatever.py"), + "*%s*" % p1, "*not the same*", - "*%s*" % b.join("test_whatever.py"), + "*%s*" % p2, "*HINT*", ] ) - def test_import_prepend_append(self, testdir, monkeypatch): - root1 = testdir.mkdir("root1") - root2 = testdir.mkdir("root2") - root1.ensure("x456.py") - root2.ensure("x456.py") - p = root2.join("test_x456.py") + def test_import_prepend_append( + self, pytester: Pytester, monkeypatch: MonkeyPatch + ) -> None: + root1 = pytester.mkdir("root1") + root2 = pytester.mkdir("root2") + root1.joinpath("x456.py").touch() + root2.joinpath("x456.py").touch() + p = root2.joinpath("test_x456.py") monkeypatch.syspath_prepend(str(root1)) - p.write( + p.write_text( textwrap.dedent( """\ import x456 @@ -52,25 +61,26 @@ def test(): ) ) ) - with root2.as_cwd(): - reprec = testdir.inline_run("--import-mode=append") + with monkeypatch.context() as mp: + mp.chdir(root2) + reprec = pytester.inline_run("--import-mode=append") reprec.assertoutcome(passed=0, failed=1) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=1) - def test_syntax_error_in_module(self, testdir): - modcol = testdir.getmodulecol("this is a syntax error") + def test_syntax_error_in_module(self, pytester: Pytester) -> None: + modcol = pytester.getmodulecol("this is a syntax error") pytest.raises(modcol.CollectError, modcol.collect) pytest.raises(modcol.CollectError, modcol.collect) - def test_module_considers_pluginmanager_at_import(self, testdir): - modcol = testdir.getmodulecol("pytest_plugins='xasdlkj',") + def test_module_considers_pluginmanager_at_import(self, pytester: Pytester) -> None: + modcol = pytester.getmodulecol("pytest_plugins='xasdlkj',") pytest.raises(ImportError, lambda: modcol.obj) - def test_invalid_test_module_name(self, testdir): - a = testdir.mkdir("a") - a.ensure("test_one.part1.py") - result = testdir.runpytest() + def test_invalid_test_module_name(self, pytester: Pytester) -> None: + a = pytester.mkdir("a") + a.joinpath("test_one.part1.py").touch() + result = pytester.runpytest() result.stdout.fnmatch_lines( [ "ImportError while importing test module*test_one.part1*", @@ -79,24 +89,26 @@ def test_invalid_test_module_name(self, testdir): ) @pytest.mark.parametrize("verbose", [0, 1, 2]) - def test_show_traceback_import_error(self, testdir, verbose): + def test_show_traceback_import_error( + self, pytester: Pytester, verbose: int + ) -> None: """Import errors when collecting modules should display the traceback (#1976). With low verbosity we omit pytest and internal modules, otherwise show all traceback entries. """ - testdir.makepyfile( + pytester.makepyfile( foo_traceback_import_error=""" from bar_traceback_import_error import NOT_AVAILABLE """, bar_traceback_import_error="", ) - testdir.makepyfile( + pytester.makepyfile( """ import foo_traceback_import_error """ ) args = ("-v",) * verbose - result = testdir.runpytest(*args) + result = pytester.runpytest(*args) result.stdout.fnmatch_lines( [ "ImportError while importing test module*", @@ -113,12 +125,12 @@ def test_show_traceback_import_error(self, testdir, verbose): else: assert "_pytest" not in stdout - def test_show_traceback_import_error_unicode(self, testdir): + def test_show_traceback_import_error_unicode(self, pytester: Pytester) -> None: """Check test modules collected which raise ImportError with unicode messages are handled properly (#2336). """ - testdir.makepyfile("raise ImportError('Something bad happened ☺')") - result = testdir.runpytest() + pytester.makepyfile("raise ImportError('Something bad happened ☺')") + result = pytester.runpytest() result.stdout.fnmatch_lines( [ "ImportError while importing test module*", @@ -130,15 +142,15 @@ def test_show_traceback_import_error_unicode(self, testdir): class TestClass: - def test_class_with_init_warning(self, testdir): - testdir.makepyfile( + def test_class_with_init_warning(self, pytester: Pytester) -> None: + pytester.makepyfile( """ class TestClass1(object): def __init__(self): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( [ "*cannot collect test class 'TestClass1' because it has " @@ -146,15 +158,15 @@ def __init__(self): ] ) - def test_class_with_new_warning(self, testdir): - testdir.makepyfile( + def test_class_with_new_warning(self, pytester: Pytester) -> None: + pytester.makepyfile( """ class TestClass1(object): def __new__(self): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( [ "*cannot collect test class 'TestClass1' because it has " @@ -162,19 +174,19 @@ def __new__(self): ] ) - def test_class_subclassobject(self, testdir): - testdir.getmodulecol( + def test_class_subclassobject(self, pytester: Pytester) -> None: + pytester.getmodulecol( """ class test(object): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*collected 0*"]) - def test_static_method(self, testdir): + def test_static_method(self, pytester: Pytester) -> None: """Support for collecting staticmethod tests (#2528, #2699)""" - testdir.getmodulecol( + pytester.getmodulecol( """ import pytest class Test(object): @@ -191,11 +203,11 @@ def test_fix(fix): assert fix == 1 """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*collected 2 items*", "*2 passed in*"]) - def test_setup_teardown_class_as_classmethod(self, testdir): - testdir.makepyfile( + def test_setup_teardown_class_as_classmethod(self, pytester: Pytester) -> None: + pytester.makepyfile( test_mod1=""" class TestClassMethod(object): @classmethod @@ -208,11 +220,11 @@ def teardown_class(cls): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*1 passed*"]) - def test_issue1035_obj_has_getattr(self, testdir): - modcol = testdir.getmodulecol( + def test_issue1035_obj_has_getattr(self, pytester: Pytester) -> None: + modcol = pytester.getmodulecol( """ class Chameleon(object): def __getattr__(self, name): @@ -223,22 +235,22 @@ def __getattr__(self, name): colitems = modcol.collect() assert len(colitems) == 0 - def test_issue1579_namedtuple(self, testdir): - testdir.makepyfile( + def test_issue1579_namedtuple(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import collections TestCase = collections.namedtuple('TestCase', ['a']) """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( "*cannot collect test class 'TestCase' " "because it has a __new__ constructor*" ) - def test_issue2234_property(self, testdir): - testdir.makepyfile( + def test_issue2234_property(self, pytester: Pytester) -> None: + pytester.makepyfile( """ class TestCase(object): @property @@ -246,20 +258,20 @@ def prop(self): raise NotImplementedError() """ ) - result = testdir.runpytest() + result = pytester.runpytest() assert result.ret == ExitCode.NO_TESTS_COLLECTED class TestFunction: - def test_getmodulecollector(self, testdir): - item = testdir.getitem("def test_func(): pass") + def test_getmodulecollector(self, pytester: Pytester) -> None: + item = pytester.getitem("def test_func(): pass") modcol = item.getparent(pytest.Module) assert isinstance(modcol, pytest.Module) assert hasattr(modcol.obj, "test_func") @pytest.mark.filterwarnings("default") - def test_function_as_object_instance_ignored(self, testdir): - testdir.makepyfile( + def test_function_as_object_instance_ignored(self, pytester: Pytester) -> None: + pytester.makepyfile( """ class A(object): def __call__(self, tmpdir): @@ -268,7 +280,7 @@ def __call__(self, tmpdir): test_a = A() """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( [ "collected 0 items", @@ -278,37 +290,37 @@ def __call__(self, tmpdir): ) @staticmethod - def make_function(testdir, **kwargs): + def make_function(pytester: Pytester, **kwargs: Any) -> Any: from _pytest.fixtures import FixtureManager - config = testdir.parseconfigure() - session = testdir.Session.from_config(config) + config = pytester.parseconfigure() + session = pytester.Session.from_config(config) session._fixturemanager = FixtureManager(session) return pytest.Function.from_parent(parent=session, **kwargs) - def test_function_equality(self, testdir): + def test_function_equality(self, pytester: Pytester) -> None: def func1(): pass def func2(): pass - f1 = self.make_function(testdir, name="name", callobj=func1) + f1 = self.make_function(pytester, name="name", callobj=func1) assert f1 == f1 f2 = self.make_function( - testdir, name="name", callobj=func2, originalname="foobar" + pytester, name="name", callobj=func2, originalname="foobar" ) assert f1 != f2 - def test_repr_produces_actual_test_id(self, testdir): + def test_repr_produces_actual_test_id(self, pytester: Pytester) -> None: f = self.make_function( - testdir, name=r"test[\xe5]", callobj=self.test_repr_produces_actual_test_id + pytester, name=r"test[\xe5]", callobj=self.test_repr_produces_actual_test_id ) assert repr(f) == r"" - def test_issue197_parametrize_emptyset(self, testdir): - testdir.makepyfile( + def test_issue197_parametrize_emptyset(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @pytest.mark.parametrize('arg', []) @@ -316,11 +328,11 @@ def test_function(arg): pass """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(skipped=1) - def test_single_tuple_unwraps_values(self, testdir): - testdir.makepyfile( + def test_single_tuple_unwraps_values(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @pytest.mark.parametrize(('arg',), [(1,)]) @@ -328,11 +340,11 @@ def test_function(arg): assert arg == 1 """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=1) - def test_issue213_parametrize_value_no_equal(self, testdir): - testdir.makepyfile( + def test_issue213_parametrize_value_no_equal(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest class A(object): @@ -343,12 +355,12 @@ def test_function(arg): assert arg.__class__.__name__ == "A" """ ) - reprec = testdir.inline_run("--fulltrace") + reprec = pytester.inline_run("--fulltrace") reprec.assertoutcome(passed=1) - def test_parametrize_with_non_hashable_values(self, testdir): + def test_parametrize_with_non_hashable_values(self, pytester: Pytester) -> None: """Test parametrization with non-hashable values.""" - testdir.makepyfile( + pytester.makepyfile( """ archival_mapping = { '1.0': {'tag': '1.0'}, @@ -363,12 +375,14 @@ def test_archival_to_version(key, value): assert value == archival_mapping[key] """ ) - rec = testdir.inline_run() + rec = pytester.inline_run() rec.assertoutcome(passed=2) - def test_parametrize_with_non_hashable_values_indirect(self, testdir): + def test_parametrize_with_non_hashable_values_indirect( + self, pytester: Pytester + ) -> None: """Test parametrization with non-hashable values with indirect parametrization.""" - testdir.makepyfile( + pytester.makepyfile( """ archival_mapping = { '1.0': {'tag': '1.0'}, @@ -392,12 +406,12 @@ def test_archival_to_version(key, value): assert value == archival_mapping[key] """ ) - rec = testdir.inline_run() + rec = pytester.inline_run() rec.assertoutcome(passed=2) - def test_parametrize_overrides_fixture(self, testdir): + def test_parametrize_overrides_fixture(self, pytester: Pytester) -> None: """Test parametrization when parameter overrides existing fixture with same name.""" - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -421,12 +435,14 @@ def test_overridden_via_multiparam(other, value): assert value == 'overridden' """ ) - rec = testdir.inline_run() + rec = pytester.inline_run() rec.assertoutcome(passed=3) - def test_parametrize_overrides_parametrized_fixture(self, testdir): + def test_parametrize_overrides_parametrized_fixture( + self, pytester: Pytester + ) -> None: """Test parametrization when parameter overrides existing parametrized fixture with same name.""" - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -440,12 +456,14 @@ def test_overridden_via_param(value): assert value == 'overridden' """ ) - rec = testdir.inline_run() + rec = pytester.inline_run() rec.assertoutcome(passed=1) - def test_parametrize_overrides_indirect_dependency_fixture(self, testdir): + def test_parametrize_overrides_indirect_dependency_fixture( + self, pytester: Pytester + ) -> None: """Test parametrization when parameter overrides a fixture that a test indirectly depends on""" - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -471,11 +489,11 @@ def test_it(fix1): assert not fix3_instantiated """ ) - rec = testdir.inline_run() + rec = pytester.inline_run() rec.assertoutcome(passed=1) - def test_parametrize_with_mark(self, testdir): - items = testdir.getitems( + def test_parametrize_with_mark(self, pytester: Pytester) -> None: + items = pytester.getitems( """ import pytest @pytest.mark.foo @@ -495,8 +513,8 @@ def test_function(arg): ) assert "foo" in keywords[1] and "bar" in keywords[1] and "baz" in keywords[1] - def test_parametrize_with_empty_string_arguments(self, testdir): - items = testdir.getitems( + def test_parametrize_with_empty_string_arguments(self, pytester: Pytester) -> None: + items = pytester.getitems( """\ import pytest @@ -508,8 +526,8 @@ def test(v, w): ... names = {item.name for item in items} assert names == {"test[-]", "test[ -]", "test[- ]", "test[ - ]"} - def test_function_equality_with_callspec(self, testdir): - items = testdir.getitems( + def test_function_equality_with_callspec(self, pytester: Pytester) -> None: + items = pytester.getitems( """ import pytest @pytest.mark.parametrize('arg', [1,2]) @@ -520,8 +538,8 @@ def test_function(arg): assert items[0] != items[1] assert not (items[0] == items[1]) - def test_pyfunc_call(self, testdir): - item = testdir.getitem("def test_func(): raise ValueError") + def test_pyfunc_call(self, pytester: Pytester) -> None: + item = pytester.getitem("def test_func(): raise ValueError") config = item.config class MyPlugin1: @@ -537,8 +555,8 @@ def pytest_pyfunc_call(self): config.hook.pytest_runtest_setup(item=item) config.hook.pytest_pyfunc_call(pyfuncitem=item) - def test_multiple_parametrize(self, testdir): - modcol = testdir.getmodulecol( + def test_multiple_parametrize(self, pytester: Pytester) -> None: + modcol = pytester.getmodulecol( """ import pytest @pytest.mark.parametrize('x', [0, 1]) @@ -553,8 +571,8 @@ def test1(x, y): assert colitems[2].name == "test1[3-0]" assert colitems[3].name == "test1[3-1]" - def test_issue751_multiple_parametrize_with_ids(self, testdir): - modcol = testdir.getmodulecol( + def test_issue751_multiple_parametrize_with_ids(self, pytester: Pytester) -> None: + modcol = pytester.getmodulecol( """ import pytest @pytest.mark.parametrize('x', [0], ids=['c']) @@ -572,8 +590,8 @@ def test2(self, x, y): assert colitems[2].name == "test2[a-c]" assert colitems[3].name == "test2[b-c]" - def test_parametrize_skipif(self, testdir): - testdir.makepyfile( + def test_parametrize_skipif(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -584,11 +602,11 @@ def test_skip_if(x): assert x < 2 """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["* 2 passed, 1 skipped in *"]) - def test_parametrize_skip(self, testdir): - testdir.makepyfile( + def test_parametrize_skip(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -599,11 +617,11 @@ def test_skip(x): assert x < 2 """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["* 2 passed, 1 skipped in *"]) - def test_parametrize_skipif_no_skip(self, testdir): - testdir.makepyfile( + def test_parametrize_skipif_no_skip(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -614,11 +632,11 @@ def test_skipif_no_skip(x): assert x < 2 """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["* 1 failed, 2 passed in *"]) - def test_parametrize_xfail(self, testdir): - testdir.makepyfile( + def test_parametrize_xfail(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -629,11 +647,11 @@ def test_xfail(x): assert x < 2 """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["* 2 passed, 1 xfailed in *"]) - def test_parametrize_passed(self, testdir): - testdir.makepyfile( + def test_parametrize_passed(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -644,11 +662,11 @@ def test_xfail(x): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["* 2 passed, 1 xpassed in *"]) - def test_parametrize_xfail_passed(self, testdir): - testdir.makepyfile( + def test_parametrize_xfail_passed(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -659,11 +677,11 @@ def test_passed(x): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["* 3 passed in *"]) - def test_function_originalname(self, testdir: Testdir) -> None: - items = testdir.getitems( + def test_function_originalname(self, pytester: Pytester) -> None: + items = pytester.getitems( """ import pytest @@ -685,14 +703,14 @@ def test_no_param(): "test_no_param", ] - def test_function_with_square_brackets(self, testdir: Testdir) -> None: + def test_function_with_square_brackets(self, pytester: Pytester) -> None: """Check that functions with square brackets don't cause trouble.""" - p1 = testdir.makepyfile( + p1 = pytester.makepyfile( """ locals()["test_foo[name]"] = lambda: None """ ) - result = testdir.runpytest("-v", str(p1)) + result = pytester.runpytest("-v", str(p1)) result.stdout.fnmatch_lines( [ "test_function_with_square_brackets.py::test_foo[[]name[]] PASSED *", @@ -702,23 +720,23 @@ def test_function_with_square_brackets(self, testdir: Testdir) -> None: class TestSorting: - def test_check_equality(self, testdir) -> None: - modcol = testdir.getmodulecol( + def test_check_equality(self, pytester: Pytester) -> None: + modcol = pytester.getmodulecol( """ def test_pass(): pass def test_fail(): assert 0 """ ) - fn1 = testdir.collect_by_name(modcol, "test_pass") + fn1 = pytester.collect_by_name(modcol, "test_pass") assert isinstance(fn1, pytest.Function) - fn2 = testdir.collect_by_name(modcol, "test_pass") + fn2 = pytester.collect_by_name(modcol, "test_pass") assert isinstance(fn2, pytest.Function) assert fn1 == fn2 assert fn1 != modcol assert hash(fn1) == hash(fn2) - fn3 = testdir.collect_by_name(modcol, "test_fail") + fn3 = pytester.collect_by_name(modcol, "test_fail") assert isinstance(fn3, pytest.Function) assert not (fn1 == fn3) assert fn1 != fn3 @@ -730,8 +748,8 @@ def test_fail(): assert 0 assert [1, 2, 3] != fn # type: ignore[comparison-overlap] assert modcol != fn - def test_allow_sane_sorting_for_decorators(self, testdir): - modcol = testdir.getmodulecol( + def test_allow_sane_sorting_for_decorators(self, pytester: Pytester) -> None: + modcol = pytester.getmodulecol( """ def dec(f): g = lambda: f(2) @@ -754,8 +772,8 @@ def test_a(y): class TestConftestCustomization: - def test_pytest_pycollect_module(self, testdir): - testdir.makeconftest( + def test_pytest_pycollect_module(self, pytester: Pytester) -> None: + pytester.makeconftest( """ import pytest class MyModule(pytest.Module): @@ -765,14 +783,15 @@ def pytest_pycollect_makemodule(path, parent): return MyModule.from_parent(fspath=path, parent=parent) """ ) - testdir.makepyfile("def test_some(): pass") - testdir.makepyfile(test_xyz="def test_func(): pass") - result = testdir.runpytest("--collect-only") + pytester.makepyfile("def test_some(): pass") + pytester.makepyfile(test_xyz="def test_func(): pass") + result = pytester.runpytest("--collect-only") result.stdout.fnmatch_lines(["* None: + b = pytester.path.joinpath("a", "b") + b.mkdir(parents=True) + b.joinpath("conftest.py").write_text( textwrap.dedent( """\ import pytest @@ -784,7 +803,7 @@ def pytest_pycollect_makemodule(): """ ) ) - b.join("test_module.py").write( + b.joinpath("test_module.py").write_text( textwrap.dedent( """\ def test_hello(): @@ -792,12 +811,13 @@ def test_hello(): """ ) ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=1) - def test_customized_pymakeitem(self, testdir): - b = testdir.mkdir("a").mkdir("b") - b.join("conftest.py").write( + def test_customized_pymakeitem(self, pytester: Pytester) -> None: + b = pytester.path.joinpath("a", "b") + b.mkdir(parents=True) + b.joinpath("conftest.py").write_text( textwrap.dedent( """\ import pytest @@ -812,7 +832,7 @@ def pytest_pycollect_makeitem(): """ ) ) - b.join("test_module.py").write( + b.joinpath("test_module.py").write_text( textwrap.dedent( """\ import pytest @@ -825,11 +845,11 @@ def test_hello(obj): """ ) ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=1) - def test_pytest_pycollect_makeitem(self, testdir): - testdir.makeconftest( + def test_pytest_pycollect_makeitem(self, pytester: Pytester) -> None: + pytester.makeconftest( """ import pytest class MyFunction(pytest.Function): @@ -839,16 +859,16 @@ def pytest_pycollect_makeitem(collector, name, obj): return MyFunction.from_parent(name=name, parent=collector) """ ) - testdir.makepyfile("def some(): pass") - result = testdir.runpytest("--collect-only") + pytester.makepyfile("def some(): pass") + result = pytester.runpytest("--collect-only") result.stdout.fnmatch_lines(["*MyFunction*some*"]) - def test_issue2369_collect_module_fileext(self, testdir): + def test_issue2369_collect_module_fileext(self, pytester: Pytester) -> None: """Ensure we can collect files with weird file extensions as Python modules (#2369)""" # We'll implement a little finder and loader to import files containing # Python source code whose file extension is ".narf". - testdir.makeconftest( + pytester.makeconftest( """ import sys, os, imp from _pytest.python import Module @@ -866,17 +886,17 @@ def pytest_collect_file(path, parent): if path.ext == ".narf": return Module.from_parent(fspath=path, parent=parent)""" ) - testdir.makefile( + pytester.makefile( ".narf", """\ def test_something(): assert 1 + 1 == 2""", ) # Use runpytest_subprocess, since we're futzing with sys.meta_path. - result = testdir.runpytest_subprocess() + result = pytester.runpytest_subprocess() result.stdout.fnmatch_lines(["*1 passed*"]) - def test_early_ignored_attributes(self, testdir: Testdir) -> None: + def test_early_ignored_attributes(self, pytester: Pytester) -> None: """Builtin attributes should be ignored early on, even if configuration would otherwise allow them. @@ -884,14 +904,14 @@ def test_early_ignored_attributes(self, testdir: Testdir) -> None: although it tests PytestCollectionWarning is not raised, while it would have been raised otherwise. """ - testdir.makeini( + pytester.makeini( """ [pytest] python_classes=* python_functions=* """ ) - testdir.makepyfile( + pytester.makepyfile( """ class TestEmpty: pass @@ -900,15 +920,15 @@ def test_real(): pass """ ) - items, rec = testdir.inline_genitems() + items, rec = pytester.inline_genitems() assert rec.ret == 0 assert len(items) == 1 -def test_setup_only_available_in_subdir(testdir): - sub1 = testdir.mkpydir("sub1") - sub2 = testdir.mkpydir("sub2") - sub1.join("conftest.py").write( +def test_setup_only_available_in_subdir(pytester: Pytester) -> None: + sub1 = pytester.mkpydir("sub1") + sub2 = pytester.mkpydir("sub2") + sub1.joinpath("conftest.py").write_text( textwrap.dedent( """\ import pytest @@ -921,7 +941,7 @@ def pytest_runtest_teardown(item): """ ) ) - sub2.join("conftest.py").write( + sub2.joinpath("conftest.py").write_text( textwrap.dedent( """\ import pytest @@ -934,14 +954,14 @@ def pytest_runtest_teardown(item): """ ) ) - sub1.join("test_in_sub1.py").write("def test_1(): pass") - sub2.join("test_in_sub2.py").write("def test_2(): pass") - result = testdir.runpytest("-v", "-s") + sub1.joinpath("test_in_sub1.py").write_text("def test_1(): pass") + sub2.joinpath("test_in_sub2.py").write_text("def test_2(): pass") + result = pytester.runpytest("-v", "-s") result.assert_outcomes(passed=2) -def test_modulecol_roundtrip(testdir): - modcol = testdir.getmodulecol("pass", withinit=False) +def test_modulecol_roundtrip(pytester: Pytester) -> None: + modcol = pytester.getmodulecol("pass", withinit=False) trail = modcol.nodeid newcol = modcol.session.perform_collect([trail], genitems=0)[0] assert modcol.name == newcol.name @@ -956,8 +976,8 @@ def test_skip_simple(self): assert excinfo.traceback[-2].frame.code.name == "test_skip_simple" assert not excinfo.traceback[-2].ishidden() - def test_traceback_argsetup(self, testdir): - testdir.makeconftest( + def test_traceback_argsetup(self, pytester: Pytester) -> None: + pytester.makeconftest( """ import pytest @@ -966,8 +986,8 @@ def hello(request): raise ValueError("xyz") """ ) - p = testdir.makepyfile("def test(hello): pass") - result = testdir.runpytest(p) + p = pytester.makepyfile("def test(hello): pass") + result = pytester.runpytest(p) assert result.ret != 0 out = result.stdout.str() assert "xyz" in out @@ -975,14 +995,14 @@ def hello(request): numentries = out.count("_ _ _") # separator for traceback entries assert numentries == 0 - result = testdir.runpytest("--fulltrace", p) + result = pytester.runpytest("--fulltrace", p) out = result.stdout.str() assert "conftest.py:5: ValueError" in out numentries = out.count("_ _ _ _") # separator for traceback entries assert numentries > 3 - def test_traceback_error_during_import(self, testdir): - testdir.makepyfile( + def test_traceback_error_during_import(self, pytester: Pytester) -> None: + pytester.makepyfile( """ x = 1 x = 2 @@ -990,21 +1010,23 @@ def test_traceback_error_during_import(self, testdir): asd """ ) - result = testdir.runpytest() + result = pytester.runpytest() assert result.ret != 0 out = result.stdout.str() assert "x = 1" not in out assert "x = 2" not in out result.stdout.fnmatch_lines([" *asd*", "E*NameError*"]) - result = testdir.runpytest("--fulltrace") + result = pytester.runpytest("--fulltrace") out = result.stdout.str() assert "x = 1" in out assert "x = 2" in out result.stdout.fnmatch_lines([">*asd*", "E*NameError*"]) - def test_traceback_filter_error_during_fixture_collection(self, testdir): + def test_traceback_filter_error_during_fixture_collection( + self, pytester: Pytester + ) -> None: """Integration test for issue #995.""" - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -1022,7 +1044,7 @@ def test_failing_fixture(fail_fixture): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() assert result.ret != 0 out = result.stdout.str() assert "INTERNALERROR>" not in out @@ -1039,6 +1061,7 @@ def test_filter_traceback_generated_code(self) -> None: """ from _pytest._code import filter_traceback + tb = None try: ns: Dict[str, Any] = {} exec("def foo(): raise ValueError", ns) @@ -1051,7 +1074,7 @@ def test_filter_traceback_generated_code(self) -> None: assert isinstance(traceback[-1].path, str) assert not filter_traceback(traceback[-1]) - def test_filter_traceback_path_no_longer_valid(self, testdir) -> None: + def test_filter_traceback_path_no_longer_valid(self, pytester: Pytester) -> None: """Test that filter_traceback() works with the fact that _pytest._code.code.Code.path attribute might return an str object. @@ -1060,13 +1083,14 @@ def test_filter_traceback_path_no_longer_valid(self, testdir) -> None: """ from _pytest._code import filter_traceback - testdir.syspathinsert() - testdir.makepyfile( + pytester.syspathinsert() + pytester.makepyfile( filter_traceback_entry_as_str=""" def foo(): raise ValueError """ ) + tb = None try: import filter_traceback_entry_as_str @@ -1075,15 +1099,15 @@ def foo(): _, _, tb = sys.exc_info() assert tb is not None - testdir.tmpdir.join("filter_traceback_entry_as_str.py").remove() + pytester.path.joinpath("filter_traceback_entry_as_str.py").unlink() traceback = _pytest._code.Traceback(tb) assert isinstance(traceback[-1].path, str) assert filter_traceback(traceback[-1]) class TestReportInfo: - def test_itemreport_reportinfo(self, testdir): - testdir.makeconftest( + def test_itemreport_reportinfo(self, pytester: Pytester) -> None: + pytester.makeconftest( """ import pytest class MyFunction(pytest.Function): @@ -1094,26 +1118,27 @@ def pytest_pycollect_makeitem(collector, name, obj): return MyFunction.from_parent(name=name, parent=collector) """ ) - item = testdir.getitem("def test_func(): pass") + item = pytester.getitem("def test_func(): pass") item.config.pluginmanager.getplugin("runner") assert item.location == ("ABCDE", 42, "custom") - def test_func_reportinfo(self, testdir): - item = testdir.getitem("def test_func(): pass") + def test_func_reportinfo(self, pytester: Pytester) -> None: + item = pytester.getitem("def test_func(): pass") fspath, lineno, modpath = item.reportinfo() assert fspath == item.fspath assert lineno == 0 assert modpath == "test_func" - def test_class_reportinfo(self, testdir): - modcol = testdir.getmodulecol( + def test_class_reportinfo(self, pytester: Pytester) -> None: + modcol = pytester.getmodulecol( """ # lineno 0 class TestClass(object): def test_hello(self): pass """ ) - classcol = testdir.collect_by_name(modcol, "TestClass") + classcol = pytester.collect_by_name(modcol, "TestClass") + assert isinstance(classcol, Class) fspath, lineno, msg = classcol.reportinfo() assert fspath == modcol.fspath assert lineno == 1 @@ -1122,26 +1147,28 @@ def test_hello(self): pass @pytest.mark.filterwarnings( "ignore:usage of Generator.Function is deprecated, please use pytest.Function instead" ) - def test_reportinfo_with_nasty_getattr(self, testdir): + def test_reportinfo_with_nasty_getattr(self, pytester: Pytester) -> None: # https://github.com/pytest-dev/pytest/issues/1204 - modcol = testdir.getmodulecol( + modcol = pytester.getmodulecol( """ # lineno 0 class TestClass(object): def __getattr__(self, name): return "this is not an int" - def test_foo(self): + def intest_foo(self): pass """ ) - classcol = testdir.collect_by_name(modcol, "TestClass") - instance = classcol.collect()[0] + classcol = pytester.collect_by_name(modcol, "TestClass") + assert isinstance(classcol, Class) + instance = list(classcol.collect())[0] + assert isinstance(instance, Instance) fspath, lineno, msg = instance.reportinfo() -def test_customized_python_discovery(testdir): - testdir.makeini( +def test_customized_python_discovery(pytester: Pytester) -> None: + pytester.makeini( """ [pytest] python_files=check_*.py @@ -1149,7 +1176,7 @@ def test_customized_python_discovery(testdir): python_functions=check """ ) - p = testdir.makepyfile( + p = pytester.makepyfile( """ def check_simple(): pass @@ -1158,41 +1185,41 @@ def check_meth(self): pass """ ) - p2 = p.new(basename=p.basename.replace("test", "check")) - p.move(p2) - result = testdir.runpytest("--collect-only", "-s") + p2 = p.with_name(p.name.replace("test", "check")) + p.rename(p2) + result = pytester.runpytest("--collect-only", "-s") result.stdout.fnmatch_lines( ["*check_customized*", "*check_simple*", "*CheckMyApp*", "*check_meth*"] ) - result = testdir.runpytest() + result = pytester.runpytest() assert result.ret == 0 result.stdout.fnmatch_lines(["*2 passed*"]) -def test_customized_python_discovery_functions(testdir): - testdir.makeini( +def test_customized_python_discovery_functions(pytester: Pytester) -> None: + pytester.makeini( """ [pytest] python_functions=_test """ ) - testdir.makepyfile( + pytester.makepyfile( """ def _test_underscore(): pass """ ) - result = testdir.runpytest("--collect-only", "-s") + result = pytester.runpytest("--collect-only", "-s") result.stdout.fnmatch_lines(["*_test_underscore*"]) - result = testdir.runpytest() + result = pytester.runpytest() assert result.ret == 0 result.stdout.fnmatch_lines(["*1 passed*"]) -def test_unorderable_types(testdir): - testdir.makepyfile( +def test_unorderable_types(pytester: Pytester) -> None: + pytester.makepyfile( """ class TestJoinEmpty(object): pass @@ -1205,19 +1232,19 @@ class Test(object): TestFoo = make_test() """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.no_fnmatch_line("*TypeError*") assert result.ret == ExitCode.NO_TESTS_COLLECTED @pytest.mark.filterwarnings("default") -def test_dont_collect_non_function_callable(testdir): +def test_dont_collect_non_function_callable(pytester: Pytester) -> None: """Test for issue https://github.com/pytest-dev/pytest/issues/331 In this case an INTERNALERROR occurred trying to report the failure of a test like this one because pytest failed to get the source lines. """ - testdir.makepyfile( + pytester.makepyfile( """ class Oh(object): def __call__(self): @@ -1229,7 +1256,7 @@ def test_real(): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( [ "*collected 1 item*", @@ -1239,21 +1266,21 @@ def test_real(): ) -def test_class_injection_does_not_break_collection(testdir): +def test_class_injection_does_not_break_collection(pytester: Pytester) -> None: """Tests whether injection during collection time will terminate testing. In this case the error should not occur if the TestClass itself is modified during collection time, and the original method list is still used for collection. """ - testdir.makeconftest( + pytester.makeconftest( """ from test_inject import TestClass def pytest_generate_tests(metafunc): TestClass.changed_var = {} """ ) - testdir.makepyfile( + pytester.makepyfile( test_inject=''' class TestClass(object): def test_injection(self): @@ -1261,7 +1288,7 @@ def test_injection(self): pass ''' ) - result = testdir.runpytest() + result = pytester.runpytest() assert ( "RuntimeError: dictionary changed size during iteration" not in result.stdout.str() @@ -1269,16 +1296,16 @@ def test_injection(self): result.stdout.fnmatch_lines(["*1 passed*"]) -def test_syntax_error_with_non_ascii_chars(testdir): +def test_syntax_error_with_non_ascii_chars(pytester: Pytester) -> None: """Fix decoding issue while formatting SyntaxErrors during collection (#578).""" - testdir.makepyfile("☃") - result = testdir.runpytest() + pytester.makepyfile("☃") + result = pytester.runpytest() result.stdout.fnmatch_lines(["*ERROR collecting*", "*SyntaxError*", "*1 error in*"]) -def test_collect_error_with_fulltrace(testdir): - testdir.makepyfile("assert 0") - result = testdir.runpytest("--fulltrace") +def test_collect_error_with_fulltrace(pytester: Pytester) -> None: + pytester.makepyfile("assert 0") + result = pytester.runpytest("--fulltrace") result.stdout.fnmatch_lines( [ "collected 0 items / 1 error", @@ -1295,14 +1322,14 @@ def test_collect_error_with_fulltrace(testdir): ) -def test_skip_duplicates_by_default(testdir): +def test_skip_duplicates_by_default(pytester: Pytester) -> None: """Test for issue https://github.com/pytest-dev/pytest/issues/1609 (#1609) Ignore duplicate directories. """ - a = testdir.mkdir("a") - fh = a.join("test_a.py") - fh.write( + a = pytester.mkdir("a") + fh = a.joinpath("test_a.py") + fh.write_text( textwrap.dedent( """\ import pytest @@ -1311,18 +1338,18 @@ def test_real(): """ ) ) - result = testdir.runpytest(a.strpath, a.strpath) + result = pytester.runpytest(str(a), str(a)) result.stdout.fnmatch_lines(["*collected 1 item*"]) -def test_keep_duplicates(testdir): +def test_keep_duplicates(pytester: Pytester) -> None: """Test for issue https://github.com/pytest-dev/pytest/issues/1609 (#1609) Use --keep-duplicates to collect tests from duplicate directories. """ - a = testdir.mkdir("a") - fh = a.join("test_a.py") - fh.write( + a = pytester.mkdir("a") + fh = a.joinpath("test_a.py") + fh.write_text( textwrap.dedent( """\ import pytest @@ -1331,24 +1358,24 @@ def test_real(): """ ) ) - result = testdir.runpytest("--keep-duplicates", a.strpath, a.strpath) + result = pytester.runpytest("--keep-duplicates", str(a), str(a)) result.stdout.fnmatch_lines(["*collected 2 item*"]) -def test_package_collection_infinite_recursion(testdir): - testdir.copy_example("collect/package_infinite_recursion") - result = testdir.runpytest() +def test_package_collection_infinite_recursion(pytester: Pytester) -> None: + pytester.copy_example("collect/package_infinite_recursion") + result = pytester.runpytest() result.stdout.fnmatch_lines(["*1 passed*"]) -def test_package_collection_init_given_as_argument(testdir): +def test_package_collection_init_given_as_argument(pytester: Pytester) -> None: """Regression test for #3749""" - p = testdir.copy_example("collect/package_init_given_as_arg") - result = testdir.runpytest(p / "pkg" / "__init__.py") + p = pytester.copy_example("collect/package_init_given_as_arg") + result = pytester.runpytest(p / "pkg" / "__init__.py") result.stdout.fnmatch_lines(["*1 passed*"]) -def test_package_with_modules(testdir): +def test_package_with_modules(pytester: Pytester) -> None: """ . └── root @@ -1363,32 +1390,35 @@ def test_package_with_modules(testdir): └── test_in_sub2.py """ - root = testdir.mkpydir("root") - sub1 = root.mkdir("sub1") - sub1.ensure("__init__.py") - sub1_test = sub1.mkdir("sub1_1") - sub1_test.ensure("__init__.py") - sub2 = root.mkdir("sub2") - sub2_test = sub2.mkdir("sub2") + root = pytester.mkpydir("root") + sub1 = root.joinpath("sub1") + sub1_test = sub1.joinpath("sub1_1") + sub1_test.mkdir(parents=True) + for d in (sub1, sub1_test): + d.joinpath("__init__.py").touch() - sub1_test.join("test_in_sub1.py").write("def test_1(): pass") - sub2_test.join("test_in_sub2.py").write("def test_2(): pass") + sub2 = root.joinpath("sub2") + sub2_test = sub2.joinpath("test") + sub2_test.mkdir(parents=True) + + sub1_test.joinpath("test_in_sub1.py").write_text("def test_1(): pass") + sub2_test.joinpath("test_in_sub2.py").write_text("def test_2(): pass") # Execute from . - result = testdir.runpytest("-v", "-s") + result = pytester.runpytest("-v", "-s") result.assert_outcomes(passed=2) # Execute from . with one argument "root" - result = testdir.runpytest("-v", "-s", "root") + result = pytester.runpytest("-v", "-s", "root") result.assert_outcomes(passed=2) # Chdir into package's root and execute with no args - root.chdir() - result = testdir.runpytest("-v", "-s") + os.chdir(root) + result = pytester.runpytest("-v", "-s") result.assert_outcomes(passed=2) -def test_package_ordering(testdir): +def test_package_ordering(pytester: Pytester) -> None: """ . └── root @@ -1402,22 +1432,24 @@ def test_package_ordering(testdir): └── test_sub2.py """ - testdir.makeini( + pytester.makeini( """ [pytest] python_files=*.py """ ) - root = testdir.mkpydir("root") - sub1 = root.mkdir("sub1") - sub1.ensure("__init__.py") - sub2 = root.mkdir("sub2") - sub2_test = sub2.mkdir("sub2") - - root.join("Test_root.py").write("def test_1(): pass") - sub1.join("Test_sub1.py").write("def test_2(): pass") - sub2_test.join("test_sub2.py").write("def test_3(): pass") + root = pytester.mkpydir("root") + sub1 = root.joinpath("sub1") + sub1.mkdir() + sub1.joinpath("__init__.py").touch() + sub2 = root.joinpath("sub2") + sub2_test = sub2.joinpath("test") + sub2_test.mkdir(parents=True) + + root.joinpath("Test_root.py").write_text("def test_1(): pass") + sub1.joinpath("Test_sub1.py").write_text("def test_2(): pass") + sub2_test.joinpath("test_sub2.py").write_text("def test_3(): pass") # Execute from . - result = testdir.runpytest("-v", "-s") + result = pytester.runpytest("-v", "-s") result.assert_outcomes(passed=3) diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index ac62de608e5..862a65abe10 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -1,3 +1,4 @@ +import os import sys import textwrap from pathlib import Path @@ -7,9 +8,10 @@ from _pytest.compat import getfuncargnames from _pytest.config import ExitCode from _pytest.fixtures import FixtureRequest +from _pytest.monkeypatch import MonkeyPatch from _pytest.pytester import get_public_names from _pytest.pytester import Pytester -from _pytest.pytester import Testdir +from _pytest.python import Function def test_getfuncargnames_functions(): @@ -91,9 +93,9 @@ def test_fillfuncargs_exposed(self): # used by oejskit, kept for compatibility assert pytest._fillfuncargs == fixtures._fillfuncargs - def test_funcarg_lookupfails(self, testdir): - testdir.copy_example() - result = testdir.runpytest() # "--collect-only") + def test_funcarg_lookupfails(self, pytester: Pytester) -> None: + pytester.copy_example() + result = pytester.runpytest() # "--collect-only") assert result.ret != 0 result.stdout.fnmatch_lines( """ @@ -103,60 +105,63 @@ def test_funcarg_lookupfails(self, testdir): """ ) - def test_detect_recursive_dependency_error(self, testdir): - testdir.copy_example() - result = testdir.runpytest() + def test_detect_recursive_dependency_error(self, pytester: Pytester) -> None: + pytester.copy_example() + result = pytester.runpytest() result.stdout.fnmatch_lines( ["*recursive dependency involving fixture 'fix1' detected*"] ) - def test_funcarg_basic(self, testdir): - testdir.copy_example() - item = testdir.getitem(Path("test_funcarg_basic.py")) + def test_funcarg_basic(self, pytester: Pytester) -> None: + pytester.copy_example() + item = pytester.getitem(Path("test_funcarg_basic.py")) + assert isinstance(item, Function) item._request._fillfixtures() del item.funcargs["request"] assert len(get_public_names(item.funcargs)) == 2 assert item.funcargs["some"] == "test_func" assert item.funcargs["other"] == 42 - def test_funcarg_lookup_modulelevel(self, testdir): - testdir.copy_example() - reprec = testdir.inline_run() + def test_funcarg_lookup_modulelevel(self, pytester: Pytester) -> None: + pytester.copy_example() + reprec = pytester.inline_run() reprec.assertoutcome(passed=2) - def test_funcarg_lookup_classlevel(self, testdir): - p = testdir.copy_example() - result = testdir.runpytest(p) + def test_funcarg_lookup_classlevel(self, pytester: Pytester) -> None: + p = pytester.copy_example() + result = pytester.runpytest(p) result.stdout.fnmatch_lines(["*1 passed*"]) - def test_conftest_funcargs_only_available_in_subdir(self, testdir): - testdir.copy_example() - result = testdir.runpytest("-v") + def test_conftest_funcargs_only_available_in_subdir( + self, pytester: Pytester + ) -> None: + pytester.copy_example() + result = pytester.runpytest("-v") result.assert_outcomes(passed=2) - def test_extend_fixture_module_class(self, testdir): - testfile = testdir.copy_example() - result = testdir.runpytest() + def test_extend_fixture_module_class(self, pytester: Pytester) -> None: + testfile = pytester.copy_example() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*1 passed*"]) - result = testdir.runpytest(testfile) + result = pytester.runpytest(testfile) result.stdout.fnmatch_lines(["*1 passed*"]) - def test_extend_fixture_conftest_module(self, testdir): - p = testdir.copy_example() - result = testdir.runpytest() + def test_extend_fixture_conftest_module(self, pytester: Pytester) -> None: + p = pytester.copy_example() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*1 passed*"]) - result = testdir.runpytest(str(next(Path(str(p)).rglob("test_*.py")))) + result = pytester.runpytest(str(next(Path(str(p)).rglob("test_*.py")))) result.stdout.fnmatch_lines(["*1 passed*"]) - def test_extend_fixture_conftest_conftest(self, testdir): - p = testdir.copy_example() - result = testdir.runpytest() + def test_extend_fixture_conftest_conftest(self, pytester: Pytester) -> None: + p = pytester.copy_example() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*1 passed*"]) - result = testdir.runpytest(str(next(Path(str(p)).rglob("test_*.py")))) + result = pytester.runpytest(str(next(Path(str(p)).rglob("test_*.py")))) result.stdout.fnmatch_lines(["*1 passed*"]) - def test_extend_fixture_conftest_plugin(self, testdir): - testdir.makepyfile( + def test_extend_fixture_conftest_plugin(self, pytester: Pytester) -> None: + pytester.makepyfile( testplugin=""" import pytest @@ -165,8 +170,8 @@ def foo(): return 7 """ ) - testdir.syspathinsert() - testdir.makeconftest( + pytester.syspathinsert() + pytester.makeconftest( """ import pytest @@ -177,18 +182,18 @@ def foo(foo): return foo + 7 """ ) - testdir.makepyfile( + pytester.makepyfile( """ def test_foo(foo): assert foo == 14 """ ) - result = testdir.runpytest("-s") + result = pytester.runpytest("-s") assert result.ret == 0 - def test_extend_fixture_plugin_plugin(self, testdir): + def test_extend_fixture_plugin_plugin(self, pytester: Pytester) -> None: # Two plugins should extend each order in loading order - testdir.makepyfile( + pytester.makepyfile( testplugin0=""" import pytest @@ -197,7 +202,7 @@ def foo(): return 7 """ ) - testdir.makepyfile( + pytester.makepyfile( testplugin1=""" import pytest @@ -206,8 +211,8 @@ def foo(foo): return foo + 7 """ ) - testdir.syspathinsert() - testdir.makepyfile( + pytester.syspathinsert() + pytester.makepyfile( """ pytest_plugins = ['testplugin0', 'testplugin1'] @@ -215,12 +220,14 @@ def test_foo(foo): assert foo == 14 """ ) - result = testdir.runpytest() + result = pytester.runpytest() assert result.ret == 0 - def test_override_parametrized_fixture_conftest_module(self, testdir): + def test_override_parametrized_fixture_conftest_module( + self, pytester: Pytester + ) -> None: """Test override of the parametrized fixture with non-parametrized one on the test module level.""" - testdir.makeconftest( + pytester.makeconftest( """ import pytest @@ -229,7 +236,7 @@ def spam(request): return request.param """ ) - testfile = testdir.makepyfile( + testfile = pytester.makepyfile( """ import pytest @@ -241,14 +248,16 @@ def test_spam(spam): assert spam == 'spam' """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*1 passed*"]) - result = testdir.runpytest(testfile) + result = pytester.runpytest(testfile) result.stdout.fnmatch_lines(["*1 passed*"]) - def test_override_parametrized_fixture_conftest_conftest(self, testdir): + def test_override_parametrized_fixture_conftest_conftest( + self, pytester: Pytester + ) -> None: """Test override of the parametrized fixture with non-parametrized one on the conftest level.""" - testdir.makeconftest( + pytester.makeconftest( """ import pytest @@ -257,8 +266,8 @@ def spam(request): return request.param """ ) - subdir = testdir.mkpydir("subdir") - subdir.join("conftest.py").write( + subdir = pytester.mkpydir("subdir") + subdir.joinpath("conftest.py").write_text( textwrap.dedent( """\ import pytest @@ -269,8 +278,8 @@ def spam(): """ ) ) - testfile = subdir.join("test_spam.py") - testfile.write( + testfile = subdir.joinpath("test_spam.py") + testfile.write_text( textwrap.dedent( """\ def test_spam(spam): @@ -278,14 +287,16 @@ def test_spam(spam): """ ) ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*1 passed*"]) - result = testdir.runpytest(testfile) + result = pytester.runpytest(testfile) result.stdout.fnmatch_lines(["*1 passed*"]) - def test_override_non_parametrized_fixture_conftest_module(self, testdir): + def test_override_non_parametrized_fixture_conftest_module( + self, pytester: Pytester + ) -> None: """Test override of the non-parametrized fixture with parametrized one on the test module level.""" - testdir.makeconftest( + pytester.makeconftest( """ import pytest @@ -294,7 +305,7 @@ def spam(): return 'spam' """ ) - testfile = testdir.makepyfile( + testfile = pytester.makepyfile( """ import pytest @@ -309,14 +320,16 @@ def test_spam(spam): params['spam'] += 1 """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*3 passed*"]) - result = testdir.runpytest(testfile) + result = pytester.runpytest(testfile) result.stdout.fnmatch_lines(["*3 passed*"]) - def test_override_non_parametrized_fixture_conftest_conftest(self, testdir): + def test_override_non_parametrized_fixture_conftest_conftest( + self, pytester: Pytester + ) -> None: """Test override of the non-parametrized fixture with parametrized one on the conftest level.""" - testdir.makeconftest( + pytester.makeconftest( """ import pytest @@ -325,8 +338,8 @@ def spam(): return 'spam' """ ) - subdir = testdir.mkpydir("subdir") - subdir.join("conftest.py").write( + subdir = pytester.mkpydir("subdir") + subdir.joinpath("conftest.py").write_text( textwrap.dedent( """\ import pytest @@ -337,8 +350,8 @@ def spam(request): """ ) ) - testfile = subdir.join("test_spam.py") - testfile.write( + testfile = subdir.joinpath("test_spam.py") + testfile.write_text( textwrap.dedent( """\ params = {'spam': 1} @@ -349,18 +362,18 @@ def test_spam(spam): """ ) ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*3 passed*"]) - result = testdir.runpytest(testfile) + result = pytester.runpytest(testfile) result.stdout.fnmatch_lines(["*3 passed*"]) def test_override_autouse_fixture_with_parametrized_fixture_conftest_conftest( - self, testdir - ): + self, pytester: Pytester + ) -> None: """Test override of the autouse fixture with parametrized one on the conftest level. This test covers the issue explained in issue 1601 """ - testdir.makeconftest( + pytester.makeconftest( """ import pytest @@ -369,8 +382,8 @@ def spam(): return 'spam' """ ) - subdir = testdir.mkpydir("subdir") - subdir.join("conftest.py").write( + subdir = pytester.mkpydir("subdir") + subdir.joinpath("conftest.py").write_text( textwrap.dedent( """\ import pytest @@ -381,8 +394,8 @@ def spam(request): """ ) ) - testfile = subdir.join("test_spam.py") - testfile.write( + testfile = subdir.joinpath("test_spam.py") + testfile.write_text( textwrap.dedent( """\ params = {'spam': 1} @@ -393,16 +406,18 @@ def test_spam(spam): """ ) ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*3 passed*"]) - result = testdir.runpytest(testfile) + result = pytester.runpytest(testfile) result.stdout.fnmatch_lines(["*3 passed*"]) - def test_override_fixture_reusing_super_fixture_parametrization(self, testdir): + def test_override_fixture_reusing_super_fixture_parametrization( + self, pytester: Pytester + ) -> None: """Override a fixture at a lower level, reusing the higher-level fixture that is parametrized (#1953). """ - testdir.makeconftest( + pytester.makeconftest( """ import pytest @@ -411,7 +426,7 @@ def foo(request): return request.param """ ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -423,14 +438,16 @@ def test_spam(foo): assert foo in (2, 4) """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*2 passed*"]) - def test_override_parametrize_fixture_and_indirect(self, testdir): + def test_override_parametrize_fixture_and_indirect( + self, pytester: Pytester + ) -> None: """Override a fixture at a lower level, reusing the higher-level fixture that is parametrized, while also using indirect parametrization. """ - testdir.makeconftest( + pytester.makeconftest( """ import pytest @@ -439,7 +456,7 @@ def foo(request): return request.param """ ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -457,14 +474,14 @@ def test_spam(bar, foo): assert foo in (2, 4) """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*2 passed*"]) def test_override_top_level_fixture_reusing_super_fixture_parametrization( - self, testdir - ): + self, pytester: Pytester + ) -> None: """Same as the above test, but with another level of overwriting.""" - testdir.makeconftest( + pytester.makeconftest( """ import pytest @@ -473,7 +490,7 @@ def foo(request): return request.param """ ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -491,15 +508,17 @@ def test_spam(self, foo): assert foo in (2, 4) """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*2 passed*"]) - def test_override_parametrized_fixture_with_new_parametrized_fixture(self, testdir): + def test_override_parametrized_fixture_with_new_parametrized_fixture( + self, pytester: Pytester + ) -> None: """Overriding a parametrized fixture, while also parametrizing the new fixture and simultaneously requesting the overwritten fixture as parameter, yields the same value as ``request.param``. """ - testdir.makeconftest( + pytester.makeconftest( """ import pytest @@ -508,7 +527,7 @@ def foo(request): return request.param """ ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -521,13 +540,13 @@ def test_spam(foo): assert foo in (20, 40) """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*2 passed*"]) - def test_autouse_fixture_plugin(self, testdir): + def test_autouse_fixture_plugin(self, pytester: Pytester) -> None: # A fixture from a plugin has no baseid set, which screwed up # the autouse fixture handling. - testdir.makepyfile( + pytester.makepyfile( testplugin=""" import pytest @@ -536,8 +555,8 @@ def foo(request): request.function.foo = 7 """ ) - testdir.syspathinsert() - testdir.makepyfile( + pytester.syspathinsert() + pytester.makepyfile( """ pytest_plugins = 'testplugin' @@ -545,11 +564,11 @@ def test_foo(request): assert request.function.foo == 7 """ ) - result = testdir.runpytest() + result = pytester.runpytest() assert result.ret == 0 - def test_funcarg_lookup_error(self, testdir): - testdir.makeconftest( + def test_funcarg_lookup_error(self, pytester: Pytester) -> None: + pytester.makeconftest( """ import pytest @@ -566,13 +585,13 @@ def c_fixture(): pass def d_fixture(): pass """ ) - testdir.makepyfile( + pytester.makepyfile( """ def test_lookup_error(unknown): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( [ "*ERROR at setup of test_lookup_error*", @@ -586,9 +605,9 @@ def test_lookup_error(unknown): ) result.stdout.no_fnmatch_line("*INTERNAL*") - def test_fixture_excinfo_leak(self, testdir): + def test_fixture_excinfo_leak(self, pytester: Pytester) -> None: # on python2 sys.excinfo would leak into fixture executions - testdir.makepyfile( + pytester.makepyfile( """ import sys import traceback @@ -607,13 +626,13 @@ def test_leak(leak): assert sys.exc_info() == (None, None, None) """ ) - result = testdir.runpytest() + result = pytester.runpytest() assert result.ret == 0 class TestRequestBasic: - def test_request_attributes(self, testdir): - item = testdir.getitem( + def test_request_attributes(self, pytester: Pytester) -> None: + item = pytester.getitem( """ import pytest @@ -622,6 +641,7 @@ def something(request): pass def test_func(something): pass """ ) + assert isinstance(item, Function) req = fixtures.FixtureRequest(item, _ispytest=True) assert req.function == item.obj assert req.keywords == item.keywords @@ -631,8 +651,8 @@ def test_func(something): pass assert req.config == item.config assert repr(req).find(req.function.__name__) != -1 - def test_request_attributes_method(self, testdir): - (item,) = testdir.getitems( + def test_request_attributes_method(self, pytester: Pytester) -> None: + (item,) = pytester.getitems( """ import pytest class TestB(object): @@ -644,12 +664,13 @@ def test_func(self, something): pass """ ) + assert isinstance(item, Function) req = item._request assert req.cls.__name__ == "TestB" assert req.instance.__class__ == req.cls - def test_request_contains_funcarg_arg2fixturedefs(self, testdir): - modcol = testdir.getmodulecol( + def test_request_contains_funcarg_arg2fixturedefs(self, pytester: Pytester) -> None: + modcol = pytester.getmodulecol( """ import pytest @pytest.fixture @@ -660,7 +681,7 @@ def test_method(self, something): pass """ ) - (item1,) = testdir.genitems([modcol]) + (item1,) = pytester.genitems([modcol]) assert item1.name == "test_method" arg2fixturedefs = fixtures.FixtureRequest( item1, _ispytest=True @@ -672,14 +693,14 @@ def test_method(self, something): hasattr(sys, "pypy_version_info"), reason="this method of test doesn't work on pypy", ) - def test_request_garbage(self, testdir): + def test_request_garbage(self, pytester: Pytester) -> None: try: import xdist # noqa except ImportError: pass else: pytest.xfail("this test is flaky when executed with xdist") - testdir.makepyfile( + pytester.makepyfile( """ import sys import pytest @@ -705,11 +726,11 @@ def test_func(): pass """ ) - result = testdir.runpytest_subprocess() + result = pytester.runpytest_subprocess() result.stdout.fnmatch_lines(["* 1 passed in *"]) - def test_getfixturevalue_recursive(self, testdir): - testdir.makeconftest( + def test_getfixturevalue_recursive(self, pytester: Pytester) -> None: + pytester.makeconftest( """ import pytest @@ -718,7 +739,7 @@ def something(request): return 1 """ ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -729,10 +750,10 @@ def test_func(something): assert something == 2 """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=1) - def test_getfixturevalue_teardown(self, testdir): + def test_getfixturevalue_teardown(self, pytester: Pytester) -> None: """ Issue #1895 @@ -743,7 +764,7 @@ def test_getfixturevalue_teardown(self, testdir): `inner` dependent on `resource` when it is used via `getfixturevalue`: `test_func` will then cause the `resource`'s finalizer to be called first because of this. """ - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -767,11 +788,11 @@ def test_func(resource): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["* 2 passed in *"]) - def test_getfixturevalue(self, testdir): - item = testdir.getitem( + def test_getfixturevalue(self, pytester: Pytester) -> None: + item = pytester.getitem( """ import pytest values = [2] @@ -783,6 +804,7 @@ def other(request): def test_func(something): pass """ ) + assert isinstance(item, Function) req = item._request with pytest.raises(pytest.FixtureLookupError): @@ -800,8 +822,8 @@ def test_func(something): pass assert len(get_public_names(item.funcargs)) == 2 assert "request" in item.funcargs - def test_request_addfinalizer(self, testdir): - item = testdir.getitem( + def test_request_addfinalizer(self, pytester: Pytester) -> None: + item = pytester.getitem( """ import pytest teardownlist = [] @@ -811,18 +833,21 @@ def something(request): def test_func(something): pass """ ) + assert isinstance(item, Function) item.session._setupstate.prepare(item) item._request._fillfixtures() # successively check finalization calls - teardownlist = item.getparent(pytest.Module).obj.teardownlist + parent = item.getparent(pytest.Module) + assert parent is not None + teardownlist = parent.obj.teardownlist ss = item.session._setupstate assert not teardownlist ss.teardown_exact(item, None) print(ss.stack) assert teardownlist == [1] - def test_request_addfinalizer_failing_setup(self, testdir): - testdir.makepyfile( + def test_request_addfinalizer_failing_setup(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest values = [1] @@ -836,11 +861,13 @@ def test_finalizer_ran(): assert not values """ ) - reprec = testdir.inline_run("-s") + reprec = pytester.inline_run("-s") reprec.assertoutcome(failed=1, passed=1) - def test_request_addfinalizer_failing_setup_module(self, testdir): - testdir.makepyfile( + def test_request_addfinalizer_failing_setup_module( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( """ import pytest values = [1, 2] @@ -853,12 +880,14 @@ def test_fix(myfix): pass """ ) - reprec = testdir.inline_run("-s") + reprec = pytester.inline_run("-s") mod = reprec.getcalls("pytest_runtest_setup")[0].item.module assert not mod.values - def test_request_addfinalizer_partial_setup_failure(self, testdir): - p = testdir.makepyfile( + def test_request_addfinalizer_partial_setup_failure( + self, pytester: Pytester + ) -> None: + p = pytester.makepyfile( """ import pytest values = [] @@ -871,17 +900,19 @@ def test_second(): assert len(values) == 1 """ ) - result = testdir.runpytest(p) + result = pytester.runpytest(p) result.stdout.fnmatch_lines( ["*1 error*"] # XXX the whole module collection fails ) - def test_request_subrequest_addfinalizer_exceptions(self, testdir): + def test_request_subrequest_addfinalizer_exceptions( + self, pytester: Pytester + ) -> None: """ Ensure exceptions raised during teardown by a finalizer are suppressed until all finalizers are called, re-raising the first exception (#2440) """ - testdir.makepyfile( + pytester.makepyfile( """ import pytest values = [] @@ -905,19 +936,19 @@ def test_second(): assert values == [3, 2, 1] """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( ["*Exception: Error in excepts fixture", "* 2 passed, 1 error in *"] ) - def test_request_getmodulepath(self, testdir): - modcol = testdir.getmodulecol("def test_somefunc(): pass") - (item,) = testdir.genitems([modcol]) + def test_request_getmodulepath(self, pytester: Pytester) -> None: + modcol = pytester.getmodulecol("def test_somefunc(): pass") + (item,) = pytester.genitems([modcol]) req = fixtures.FixtureRequest(item, _ispytest=True) assert req.fspath == modcol.fspath - def test_request_fixturenames(self, testdir): - testdir.makepyfile( + def test_request_fixturenames(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest from _pytest.pytester import get_public_names @@ -936,17 +967,17 @@ def test_function(request, farg): "tmp_path", "tmp_path_factory"]) """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=1) - def test_request_fixturenames_dynamic_fixture(self, testdir): + def test_request_fixturenames_dynamic_fixture(self, pytester: Pytester) -> None: """Regression test for #3057""" - testdir.copy_example("fixtures/test_getfixturevalue_dynamic.py") - result = testdir.runpytest() + pytester.copy_example("fixtures/test_getfixturevalue_dynamic.py") + result = pytester.runpytest() result.stdout.fnmatch_lines(["*1 passed*"]) - def test_setupdecorator_and_xunit(self, testdir): - testdir.makepyfile( + def test_setupdecorator_and_xunit(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest values = [] @@ -974,13 +1005,14 @@ def test_all(): "function", "method", "function"] """ ) - reprec = testdir.inline_run("-v") + reprec = pytester.inline_run("-v") reprec.assertoutcome(passed=3) - def test_fixtures_sub_subdir_normalize_sep(self, testdir): + def test_fixtures_sub_subdir_normalize_sep(self, pytester: Pytester) -> None: # this tests that normalization of nodeids takes place - b = testdir.mkdir("tests").mkdir("unit") - b.join("conftest.py").write( + b = pytester.path.joinpath("tests", "unit") + b.mkdir(parents=True) + b.joinpath("conftest.py").write_text( textwrap.dedent( """\ import pytest @@ -990,9 +1022,9 @@ def arg1(): """ ) ) - p = b.join("test_module.py") - p.write("def test_func(arg1): pass") - result = testdir.runpytest(p, "--fixtures") + p = b.joinpath("test_module.py") + p.write_text("def test_func(arg1): pass") + result = pytester.runpytest(p, "--fixtures") assert result.ret == 0 result.stdout.fnmatch_lines( """ @@ -1001,13 +1033,13 @@ def arg1(): """ ) - def test_show_fixtures_color_yes(self, testdir): - testdir.makepyfile("def test_this(): assert 1") - result = testdir.runpytest("--color=yes", "--fixtures") + def test_show_fixtures_color_yes(self, pytester: Pytester) -> None: + pytester.makepyfile("def test_this(): assert 1") + result = pytester.runpytest("--color=yes", "--fixtures") assert "\x1b[32mtmpdir" in result.stdout.str() - def test_newstyle_with_request(self, testdir): - testdir.makepyfile( + def test_newstyle_with_request(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @pytest.fixture() @@ -1017,11 +1049,11 @@ def test_1(arg): pass """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=1) - def test_setupcontext_no_param(self, testdir): - testdir.makepyfile( + def test_setupcontext_no_param(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @pytest.fixture(params=[1,2]) @@ -1035,13 +1067,13 @@ def test_1(arg): assert arg in (1,2) """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=2) class TestRequestMarking: - def test_applymarker(self, testdir): - item1, item2 = testdir.getitems( + def test_applymarker(self, pytester: Pytester) -> None: + item1, item2 = pytester.getitems( """ import pytest @@ -1065,8 +1097,8 @@ def test_func2(self, something): with pytest.raises(ValueError): req1.applymarker(42) # type: ignore[arg-type] - def test_accesskeywords(self, testdir): - testdir.makepyfile( + def test_accesskeywords(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @pytest.fixture() @@ -1078,11 +1110,11 @@ def test_function(keywords): assert "abc" not in keywords """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=1) - def test_accessmarker_dynamic(self, testdir): - testdir.makeconftest( + def test_accessmarker_dynamic(self, pytester: Pytester) -> None: + pytester.makeconftest( """ import pytest @pytest.fixture() @@ -1094,7 +1126,7 @@ def marking(request): request.applymarker(pytest.mark.XYZ("hello")) """ ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest def test_fun1(keywords): @@ -1105,13 +1137,13 @@ def test_fun2(keywords): assert "abc" not in keywords """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=2) class TestFixtureUsages: - def test_noargfixturedec(self, testdir): - testdir.makepyfile( + def test_noargfixturedec(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @pytest.fixture @@ -1122,11 +1154,11 @@ def test_func(arg1): assert arg1 == 1 """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=1) - def test_receives_funcargs(self, testdir): - testdir.makepyfile( + def test_receives_funcargs(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @pytest.fixture() @@ -1144,11 +1176,11 @@ def test_all(arg1, arg2): assert arg2 == 2 """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=2) - def test_receives_funcargs_scope_mismatch(self, testdir): - testdir.makepyfile( + def test_receives_funcargs_scope_mismatch(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @pytest.fixture(scope="function") @@ -1163,7 +1195,7 @@ def test_add(arg2): assert arg2 == 2 """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( [ "*ScopeMismatch*involved factories*", @@ -1173,8 +1205,10 @@ def test_add(arg2): ] ) - def test_receives_funcargs_scope_mismatch_issue660(self, testdir): - testdir.makepyfile( + def test_receives_funcargs_scope_mismatch_issue660( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( """ import pytest @pytest.fixture(scope="function") @@ -1189,13 +1223,13 @@ def test_add(arg1, arg2): assert arg2 == 2 """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( ["*ScopeMismatch*involved factories*", "* def arg2*", "*1 error*"] ) - def test_invalid_scope(self, testdir): - testdir.makepyfile( + def test_invalid_scope(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @pytest.fixture(scope="functions") @@ -1206,14 +1240,14 @@ def test_nothing(badscope): pass """ ) - result = testdir.runpytest_inprocess() + result = pytester.runpytest_inprocess() result.stdout.fnmatch_lines( "*Fixture 'badscope' from test_invalid_scope.py got an unexpected scope value 'functions'" ) @pytest.mark.parametrize("scope", ["function", "session"]) - def test_parameters_without_eq_semantics(self, scope, testdir): - testdir.makepyfile( + def test_parameters_without_eq_semantics(self, scope, pytester: Pytester) -> None: + pytester.makepyfile( """ class NoEq1: # fails on `a == b` statement def __eq__(self, _): @@ -1240,11 +1274,11 @@ def test2(no_eq): scope=scope ) ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*4 passed*"]) - def test_funcarg_parametrized_and_used_twice(self, testdir): - testdir.makepyfile( + def test_funcarg_parametrized_and_used_twice(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest values = [] @@ -1262,11 +1296,13 @@ def test_add(arg1, arg2): assert len(values) == arg1 """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*2 passed*"]) - def test_factory_uses_unknown_funcarg_as_dependency_error(self, testdir): - testdir.makepyfile( + def test_factory_uses_unknown_funcarg_as_dependency_error( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( """ import pytest @@ -1282,7 +1318,7 @@ def test_missing(call_fail): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( """ *pytest.fixture()* @@ -1293,8 +1329,8 @@ def test_missing(call_fail): """ ) - def test_factory_setup_as_classes_fails(self, testdir): - testdir.makepyfile( + def test_factory_setup_as_classes_fails(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest class arg1(object): @@ -1304,12 +1340,12 @@ def __init__(self, request): """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() values = reprec.getfailedcollections() assert len(values) == 1 - def test_usefixtures_marker(self, testdir): - testdir.makepyfile( + def test_usefixtures_marker(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -1330,17 +1366,17 @@ def test_two(self): pytest.mark.usefixtures("myfix")(TestClass) """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=2) - def test_usefixtures_ini(self, testdir): - testdir.makeini( + def test_usefixtures_ini(self, pytester: Pytester) -> None: + pytester.makeini( """ [pytest] usefixtures = myfix """ ) - testdir.makeconftest( + pytester.makeconftest( """ import pytest @@ -1350,7 +1386,7 @@ def myfix(request): """ ) - testdir.makepyfile( + pytester.makepyfile( """ class TestClass(object): def test_one(self): @@ -1359,19 +1395,19 @@ def test_two(self): assert self.hello == "world" """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=2) - def test_usefixtures_seen_in_showmarkers(self, testdir): - result = testdir.runpytest("--markers") + def test_usefixtures_seen_in_showmarkers(self, pytester: Pytester) -> None: + result = pytester.runpytest("--markers") result.stdout.fnmatch_lines( """ *usefixtures(fixturename1*mark tests*fixtures* """ ) - def test_request_instance_issue203(self, testdir): - testdir.makepyfile( + def test_request_instance_issue203(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -1384,11 +1420,11 @@ def test_hello(self, setup1): assert self.arg1 == 1 """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=1) - def test_fixture_parametrized_with_iterator(self, testdir): - testdir.makepyfile( + def test_fixture_parametrized_with_iterator(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -1411,14 +1447,14 @@ def test_2(arg2): values.append(arg2*10) """ ) - reprec = testdir.inline_run("-v") + reprec = pytester.inline_run("-v") reprec.assertoutcome(passed=4) values = reprec.getcalls("pytest_runtest_call")[0].item.module.values assert values == [1, 2, 10, 20] - def test_setup_functions_as_fixtures(self, testdir): + def test_setup_functions_as_fixtures(self, pytester: Pytester) -> None: """Ensure setup_* methods obey fixture scope rules (#517, #3094).""" - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -1452,15 +1488,14 @@ def test_printer_2(self): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["* 2 passed in *"]) class TestFixtureManagerParseFactories: @pytest.fixture - def testdir(self, request): - testdir = request.getfixturevalue("testdir") - testdir.makeconftest( + def pytester(self, pytester: Pytester) -> Pytester: + pytester.makeconftest( """ import pytest @@ -1477,10 +1512,10 @@ def item(request): return request._pyfuncitem """ ) - return testdir + return pytester - def test_parsefactories_evil_objects_issue214(self, testdir): - testdir.makepyfile( + def test_parsefactories_evil_objects_issue214(self, pytester: Pytester) -> None: + pytester.makepyfile( """ class A(object): def __call__(self): @@ -1492,11 +1527,11 @@ def test_hello(): pass """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=1, failed=0) - def test_parsefactories_conftest(self, testdir): - testdir.makepyfile( + def test_parsefactories_conftest(self, pytester: Pytester) -> None: + pytester.makepyfile( """ def test_hello(item, fm): for name in ("fm", "hello", "item"): @@ -1506,11 +1541,13 @@ def test_hello(item, fm): assert fac.func.__name__ == name """ ) - reprec = testdir.inline_run("-s") + reprec = pytester.inline_run("-s") reprec.assertoutcome(passed=1) - def test_parsefactories_conftest_and_module_and_class(self, testdir): - testdir.makepyfile( + def test_parsefactories_conftest_and_module_and_class( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( """\ import pytest @@ -1531,15 +1568,17 @@ def test_hello(self, item, fm): assert faclist[2].func(item._request) == "class" """ ) - reprec = testdir.inline_run("-s") + reprec = pytester.inline_run("-s") reprec.assertoutcome(passed=1) - def test_parsefactories_relative_node_ids(self, testdir): + def test_parsefactories_relative_node_ids( + self, pytester: Pytester, monkeypatch: MonkeyPatch + ) -> None: # example mostly taken from: # https://mail.python.org/pipermail/pytest-dev/2014-September/002617.html - runner = testdir.mkdir("runner") - package = testdir.mkdir("package") - package.join("conftest.py").write( + runner = pytester.mkdir("runner") + package = pytester.mkdir("package") + package.joinpath("conftest.py").write_text( textwrap.dedent( """\ import pytest @@ -1549,7 +1588,7 @@ def one(): """ ) ) - package.join("test_x.py").write( + package.joinpath("test_x.py").write_text( textwrap.dedent( """\ def test_x(one): @@ -1557,9 +1596,10 @@ def test_x(one): """ ) ) - sub = package.mkdir("sub") - sub.join("__init__.py").ensure() - sub.join("conftest.py").write( + sub = package.joinpath("sub") + sub.mkdir() + sub.joinpath("__init__.py").touch() + sub.joinpath("conftest.py").write_text( textwrap.dedent( """\ import pytest @@ -1569,7 +1609,7 @@ def one(): """ ) ) - sub.join("test_y.py").write( + sub.joinpath("test_y.py").write_text( textwrap.dedent( """\ def test_x(one): @@ -1577,20 +1617,21 @@ def test_x(one): """ ) ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=2) - with runner.as_cwd(): - reprec = testdir.inline_run("..") + with monkeypatch.context() as mp: + mp.chdir(runner) + reprec = pytester.inline_run("..") reprec.assertoutcome(passed=2) - def test_package_xunit_fixture(self, testdir): - testdir.makepyfile( + def test_package_xunit_fixture(self, pytester: Pytester) -> None: + pytester.makepyfile( __init__="""\ values = [] """ ) - package = testdir.mkdir("package") - package.join("__init__.py").write( + package = pytester.mkdir("package") + package.joinpath("__init__.py").write_text( textwrap.dedent( """\ from .. import values @@ -1601,7 +1642,7 @@ def teardown_module(): """ ) ) - package.join("test_x.py").write( + package.joinpath("test_x.py").write_text( textwrap.dedent( """\ from .. import values @@ -1610,8 +1651,8 @@ def test_x(): """ ) ) - package = testdir.mkdir("package2") - package.join("__init__.py").write( + package = pytester.mkdir("package2") + package.joinpath("__init__.py").write_text( textwrap.dedent( """\ from .. import values @@ -1622,7 +1663,7 @@ def teardown_module(): """ ) ) - package.join("test_x.py").write( + package.joinpath("test_x.py").write_text( textwrap.dedent( """\ from .. import values @@ -1631,19 +1672,19 @@ def test_x(): """ ) ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=2) - def test_package_fixture_complex(self, testdir): - testdir.makepyfile( + def test_package_fixture_complex(self, pytester: Pytester) -> None: + pytester.makepyfile( __init__="""\ values = [] """ ) - testdir.syspathinsert(testdir.tmpdir.dirname) - package = testdir.mkdir("package") - package.join("__init__.py").write("") - package.join("conftest.py").write( + pytester.syspathinsert(pytester.path.name) + package = pytester.mkdir("package") + package.joinpath("__init__.py").write_text("") + package.joinpath("conftest.py").write_text( textwrap.dedent( """\ import pytest @@ -1661,7 +1702,7 @@ def two(): """ ) ) - package.join("test_x.py").write( + package.joinpath("test_x.py").write_text( textwrap.dedent( """\ from .. import values @@ -1672,19 +1713,19 @@ def test_package(one): """ ) ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=2) - def test_collect_custom_items(self, testdir): - testdir.copy_example("fixtures/custom_item") - result = testdir.runpytest("foo") + def test_collect_custom_items(self, pytester: Pytester) -> None: + pytester.copy_example("fixtures/custom_item") + result = pytester.runpytest("foo") result.stdout.fnmatch_lines(["*passed*"]) class TestAutouseDiscovery: @pytest.fixture - def testdir(self, testdir): - testdir.makeconftest( + def pytester(self, pytester: Pytester) -> Pytester: + pytester.makeconftest( """ import pytest @pytest.fixture(autouse=True) @@ -1707,10 +1748,10 @@ def item(request): return request._pyfuncitem """ ) - return testdir + return pytester - def test_parsefactories_conftest(self, testdir): - testdir.makepyfile( + def test_parsefactories_conftest(self, pytester: Pytester) -> None: + pytester.makepyfile( """ from _pytest.pytester import get_public_names def test_check_setup(item, fm): @@ -1720,11 +1761,11 @@ def test_check_setup(item, fm): assert "perfunction" in autousenames """ ) - reprec = testdir.inline_run("-s") + reprec = pytester.inline_run("-s") reprec.assertoutcome(passed=1) - def test_two_classes_separated_autouse(self, testdir): - testdir.makepyfile( + def test_two_classes_separated_autouse(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest class TestA(object): @@ -1743,11 +1784,11 @@ def test_setup2(self): assert self.values == [1] """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=2) - def test_setup_at_classlevel(self, testdir): - testdir.makepyfile( + def test_setup_at_classlevel(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest class TestClass(object): @@ -1760,12 +1801,12 @@ def test_method2(self): assert self.funcname == "test_method2" """ ) - reprec = testdir.inline_run("-s") + reprec = pytester.inline_run("-s") reprec.assertoutcome(passed=2) @pytest.mark.xfail(reason="'enabled' feature not implemented") - def test_setup_enabled_functionnode(self, testdir): - testdir.makepyfile( + def test_setup_enabled_functionnode(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -1788,13 +1829,13 @@ def test_func2(request): assert "db" in request.fixturenames """ ) - reprec = testdir.inline_run("-s") + reprec = pytester.inline_run("-s") reprec.assertoutcome(passed=2) - def test_callables_nocode(self, testdir): + def test_callables_nocode(self, pytester: Pytester) -> None: """An imported mock.call would break setup/factory discovery due to it being callable and __code__ not being a code object.""" - testdir.makepyfile( + pytester.makepyfile( """ class _call(tuple): def __call__(self, *k, **kw): @@ -1805,13 +1846,13 @@ def __getattr__(self, k): call = _call() """ ) - reprec = testdir.inline_run("-s") + reprec = pytester.inline_run("-s") reprec.assertoutcome(failed=0, passed=0) - def test_autouse_in_conftests(self, testdir): - a = testdir.mkdir("a") - b = testdir.mkdir("a1") - conftest = testdir.makeconftest( + def test_autouse_in_conftests(self, pytester: Pytester) -> None: + a = pytester.mkdir("a") + b = pytester.mkdir("a1") + conftest = pytester.makeconftest( """ import pytest @pytest.fixture(autouse=True) @@ -1819,18 +1860,18 @@ def hello(): xxx """ ) - conftest.move(a.join(conftest.basename)) - a.join("test_something.py").write("def test_func(): pass") - b.join("test_otherthing.py").write("def test_func(): pass") - result = testdir.runpytest() + conftest.rename(a.joinpath(conftest.name)) + a.joinpath("test_something.py").write_text("def test_func(): pass") + b.joinpath("test_otherthing.py").write_text("def test_func(): pass") + result = pytester.runpytest() result.stdout.fnmatch_lines( """ *1 passed*1 error* """ ) - def test_autouse_in_module_and_two_classes(self, testdir): - testdir.makepyfile( + def test_autouse_in_module_and_two_classes(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest values = [] @@ -1851,14 +1892,14 @@ def test_world(self): assert values == ["module", "module", "A", "module"], values """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=3) class TestAutouseManagement: - def test_autouse_conftest_mid_directory(self, testdir): - pkgdir = testdir.mkpydir("xyz123") - pkgdir.join("conftest.py").write( + def test_autouse_conftest_mid_directory(self, pytester: Pytester) -> None: + pkgdir = pytester.mkpydir("xyz123") + pkgdir.joinpath("conftest.py").write_text( textwrap.dedent( """\ import pytest @@ -1869,8 +1910,11 @@ def app(): """ ) ) - t = pkgdir.ensure("tests", "test_app.py") - t.write( + sub = pkgdir.joinpath("tests") + sub.mkdir() + t = sub.joinpath("test_app.py") + t.touch() + t.write_text( textwrap.dedent( """\ import sys @@ -1879,11 +1923,11 @@ def test_app(): """ ) ) - reprec = testdir.inline_run("-s") + reprec = pytester.inline_run("-s") reprec.assertoutcome(passed=1) - def test_funcarg_and_setup(self, testdir): - testdir.makepyfile( + def test_funcarg_and_setup(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest values = [] @@ -1906,11 +1950,11 @@ def test_hello2(arg): assert arg == 0 """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=2) - def test_uses_parametrized_resource(self, testdir): - testdir.makepyfile( + def test_uses_parametrized_resource(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest values = [] @@ -1932,11 +1976,11 @@ def test_hello(): """ ) - reprec = testdir.inline_run("-s") + reprec = pytester.inline_run("-s") reprec.assertoutcome(passed=2) - def test_session_parametrized_function(self, testdir): - testdir.makepyfile( + def test_session_parametrized_function(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -1959,7 +2003,7 @@ def test_result(arg): assert values[:arg] == [1,2][:arg] """ ) - reprec = testdir.inline_run("-v", "-s") + reprec = pytester.inline_run("-v", "-s") reprec.assertoutcome(passed=4) def test_class_function_parametrization_finalization( @@ -2007,8 +2051,8 @@ def test_2(self): ].values assert values == ["fin_a1", "fin_a2", "fin_b1", "fin_b2"] * 2 - def test_scope_ordering(self, testdir): - testdir.makepyfile( + def test_scope_ordering(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest values = [] @@ -2027,11 +2071,11 @@ def test_method(self): assert values == [1,3,2] """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=1) - def test_parametrization_setup_teardown_ordering(self, testdir): - testdir.makepyfile( + def test_parametrization_setup_teardown_ordering(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest values = [] @@ -2056,11 +2100,11 @@ def test_finish(): "setup-2", "step1-2", "step2-2", "teardown-2",] """ ) - reprec = testdir.inline_run("-s") + reprec = pytester.inline_run("-s") reprec.assertoutcome(passed=5) - def test_ordering_autouse_before_explicit(self, testdir): - testdir.makepyfile( + def test_ordering_autouse_before_explicit(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -2075,14 +2119,16 @@ def test_hello(arg1): assert values == [1,2] """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=1) @pytest.mark.parametrize("param1", ["", "params=[1]"], ids=["p00", "p01"]) @pytest.mark.parametrize("param2", ["", "params=[1]"], ids=["p10", "p11"]) - def test_ordering_dependencies_torndown_first(self, testdir, param1, param2): + def test_ordering_dependencies_torndown_first( + self, pytester: Pytester, param1, param2 + ) -> None: """#226""" - testdir.makepyfile( + pytester.makepyfile( """ import pytest values = [] @@ -2102,13 +2148,13 @@ def test_check(): """ % locals() ) - reprec = testdir.inline_run("-s") + reprec = pytester.inline_run("-s") reprec.assertoutcome(passed=2) class TestFixtureMarker: - def test_parametrize(self, testdir): - testdir.makepyfile( + def test_parametrize(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @pytest.fixture(params=["a", "b", "c"]) @@ -2121,11 +2167,11 @@ def test_result(): assert values == list("abc") """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=4) - def test_multiple_parametrization_issue_736(self, testdir): - testdir.makepyfile( + def test_multiple_parametrization_issue_736(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -2139,19 +2185,21 @@ def test_issue(foo, foobar): assert foobar in [4,5,6] """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=9) @pytest.mark.parametrize( "param_args", ["'fixt, val'", "'fixt,val'", "['fixt', 'val']", "('fixt', 'val')"], ) - def test_override_parametrized_fixture_issue_979(self, testdir, param_args): + def test_override_parametrized_fixture_issue_979( + self, pytester: Pytester, param_args + ) -> None: """Make sure a parametrized argument can override a parametrized fixture. This was a regression introduced in the fix for #736. """ - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -2165,11 +2213,11 @@ def test_foo(fixt, val): """ % param_args ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=2) - def test_scope_session(self, testdir): - testdir.makepyfile( + def test_scope_session(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest values = [] @@ -2189,11 +2237,11 @@ def test3(self, arg): assert len(values) == 1 """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=3) - def test_scope_session_exc(self, testdir): - testdir.makepyfile( + def test_scope_session_exc(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest values = [] @@ -2210,11 +2258,11 @@ def test_last(): assert values == [1] """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(skipped=2, passed=1) - def test_scope_session_exc_two_fix(self, testdir): - testdir.makepyfile( + def test_scope_session_exc_two_fix(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest values = [] @@ -2236,11 +2284,11 @@ def test_last(): assert m == [] """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(skipped=2, passed=1) - def test_scope_exc(self, testdir): - testdir.makepyfile( + def test_scope_exc(self, pytester: Pytester) -> None: + pytester.makepyfile( test_foo=""" def test_foo(fix): pass @@ -2265,11 +2313,11 @@ def test_last(req_list): assert req_list == [1] """, ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(skipped=2, passed=1) - def test_scope_module_uses_session(self, testdir): - testdir.makepyfile( + def test_scope_module_uses_session(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest values = [] @@ -2289,11 +2337,11 @@ def test3(self, arg): assert len(values) == 1 """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=3) - def test_scope_module_and_finalizer(self, testdir): - testdir.makeconftest( + def test_scope_module_and_finalizer(self, pytester: Pytester) -> None: + pytester.makeconftest( """ import pytest finalized_list = [] @@ -2311,7 +2359,7 @@ def finalized(request): return len(finalized_list) """ ) - testdir.makepyfile( + pytester.makepyfile( test_mod1=""" def test_1(arg, created, finalized): assert created == 1 @@ -2329,11 +2377,11 @@ def test_4(arg, created, finalized): assert finalized == 2 """, ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=4) - def test_scope_mismatch_various(self, testdir): - testdir.makeconftest( + def test_scope_mismatch_various(self, pytester: Pytester) -> None: + pytester.makeconftest( """ import pytest finalized = [] @@ -2343,7 +2391,7 @@ def arg(request): pass """ ) - testdir.makepyfile( + pytester.makepyfile( test_mod1=""" import pytest @pytest.fixture(scope="session") @@ -2353,14 +2401,14 @@ def test_1(arg): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() assert result.ret != 0 result.stdout.fnmatch_lines( ["*ScopeMismatch*You tried*function*session*request*"] ) - def test_dynamic_scope(self, testdir): - testdir.makeconftest( + def test_dynamic_scope(self, pytester: Pytester) -> None: + pytester.makeconftest( """ import pytest @@ -2383,7 +2431,7 @@ def dynamic_fixture(calls=[]): """ ) - testdir.makepyfile( + pytester.makepyfile( """ def test_first(dynamic_fixture): assert dynamic_fixture == 1 @@ -2395,14 +2443,14 @@ def test_second(dynamic_fixture): """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=2) - reprec = testdir.inline_run("--extend-scope") + reprec = pytester.inline_run("--extend-scope") reprec.assertoutcome(passed=1, failed=1) - def test_dynamic_scope_bad_return(self, testdir): - testdir.makepyfile( + def test_dynamic_scope_bad_return(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -2415,14 +2463,14 @@ def fixture(): """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( "Fixture 'fixture' from test_dynamic_scope_bad_return.py " "got an unexpected scope value 'wrong-scope'" ) - def test_register_only_with_mark(self, testdir): - testdir.makeconftest( + def test_register_only_with_mark(self, pytester: Pytester) -> None: + pytester.makeconftest( """ import pytest @pytest.fixture() @@ -2430,7 +2478,7 @@ def arg(): return 1 """ ) - testdir.makepyfile( + pytester.makepyfile( test_mod1=""" import pytest @pytest.fixture() @@ -2440,11 +2488,11 @@ def test_1(arg): assert arg == 2 """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=1) - def test_parametrize_and_scope(self, testdir): - testdir.makepyfile( + def test_parametrize_and_scope(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @pytest.fixture(scope="module", params=["a", "b", "c"]) @@ -2455,7 +2503,7 @@ def test_param(arg): values.append(arg) """ ) - reprec = testdir.inline_run("-v") + reprec = pytester.inline_run("-v") reprec.assertoutcome(passed=3) values = reprec.getcalls("pytest_runtest_call")[0].item.module.values assert len(values) == 3 @@ -2463,8 +2511,8 @@ def test_param(arg): assert "b" in values assert "c" in values - def test_scope_mismatch(self, testdir): - testdir.makeconftest( + def test_scope_mismatch(self, pytester: Pytester) -> None: + pytester.makeconftest( """ import pytest @pytest.fixture(scope="function") @@ -2472,7 +2520,7 @@ def arg(request): pass """ ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest @pytest.fixture(scope="session") @@ -2482,11 +2530,11 @@ def test_mismatch(arg): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*ScopeMismatch*", "*1 error*"]) - def test_parametrize_separated_order(self, testdir): - testdir.makepyfile( + def test_parametrize_separated_order(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -2501,19 +2549,19 @@ def test_2(arg): values.append(arg) """ ) - reprec = testdir.inline_run("-v") + reprec = pytester.inline_run("-v") reprec.assertoutcome(passed=4) values = reprec.getcalls("pytest_runtest_call")[0].item.module.values assert values == [1, 1, 2, 2] - def test_module_parametrized_ordering(self, testdir): - testdir.makeini( + def test_module_parametrized_ordering(self, pytester: Pytester) -> None: + pytester.makeini( """ [pytest] console_output_style=classic """ ) - testdir.makeconftest( + pytester.makeconftest( """ import pytest @@ -2525,7 +2573,7 @@ def marg(): pass """ ) - testdir.makepyfile( + pytester.makepyfile( test_mod1=""" def test_func(sarg): pass @@ -2543,7 +2591,7 @@ def test_func4(marg): pass """, ) - result = testdir.runpytest("-v") + result = pytester.runpytest("-v") result.stdout.fnmatch_lines( """ test_mod1.py::test_func[s1] PASSED @@ -2565,14 +2613,14 @@ def test_func4(marg): """ ) - def test_dynamic_parametrized_ordering(self, testdir): - testdir.makeini( + def test_dynamic_parametrized_ordering(self, pytester: Pytester) -> None: + pytester.makeini( """ [pytest] console_output_style=classic """ ) - testdir.makeconftest( + pytester.makeconftest( """ import pytest @@ -2592,7 +2640,7 @@ def reprovision(request, flavor, encap): pass """ ) - testdir.makepyfile( + pytester.makepyfile( """ def test(reprovision): pass @@ -2600,7 +2648,7 @@ def test2(reprovision): pass """ ) - result = testdir.runpytest("-v") + result = pytester.runpytest("-v") result.stdout.fnmatch_lines( """ test_dynamic_parametrized_ordering.py::test[flavor1-vxlan] PASSED @@ -2614,14 +2662,14 @@ def test2(reprovision): """ ) - def test_class_ordering(self, testdir): - testdir.makeini( + def test_class_ordering(self, pytester: Pytester) -> None: + pytester.makeini( """ [pytest] console_output_style=classic """ ) - testdir.makeconftest( + pytester.makeconftest( """ import pytest @@ -2642,7 +2690,7 @@ def fin(): request.addfinalizer(fin) """ ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -2656,7 +2704,7 @@ def test_3(self): pass """ ) - result = testdir.runpytest("-vs") + result = pytester.runpytest("-vs") result.stdout.re_match_lines( r""" test_class_ordering.py::TestClass2::test_1\[a-1\] PASSED @@ -2674,8 +2722,10 @@ def test_3(self): """ ) - def test_parametrize_separated_order_higher_scope_first(self, testdir): - testdir.makepyfile( + def test_parametrize_separated_order_higher_scope_first( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( """ import pytest @@ -2704,7 +2754,7 @@ def test_4(modarg, arg): values.append("test4") """ ) - reprec = testdir.inline_run("-v") + reprec = pytester.inline_run("-v") reprec.assertoutcome(passed=12) values = reprec.getcalls("pytest_runtest_call")[0].item.module.values expected = [ @@ -2750,8 +2800,8 @@ def test_4(modarg, arg): pprint.pprint(list(zip(values, expected))) assert values == expected - def test_parametrized_fixture_teardown_order(self, testdir): - testdir.makepyfile( + def test_parametrized_fixture_teardown_order(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @pytest.fixture(params=[1,2], scope="class") @@ -2783,7 +2833,7 @@ def test_finish(): assert not values """ ) - result = testdir.runpytest("-v") + result = pytester.runpytest("-v") result.stdout.fnmatch_lines( """ *3 passed* @@ -2791,8 +2841,8 @@ def test_finish(): ) result.stdout.no_fnmatch_line("*error*") - def test_fixture_finalizer(self, testdir): - testdir.makeconftest( + def test_fixture_finalizer(self, pytester: Pytester) -> None: + pytester.makeconftest( """ import pytest import sys @@ -2801,13 +2851,13 @@ def test_fixture_finalizer(self, testdir): def browser(request): def finalize(): - sys.stdout.write('Finalized') + sys.stdout.write_text('Finalized') request.addfinalizer(finalize) return {} """ ) - b = testdir.mkdir("subdir") - b.join("test_overridden_fixture_finalizer.py").write( + b = pytester.mkdir("subdir") + b.joinpath("test_overridden_fixture_finalizer.py").write_text( textwrap.dedent( """\ import pytest @@ -2821,12 +2871,12 @@ def test_browser(browser): """ ) ) - reprec = testdir.runpytest("-s") + reprec = pytester.runpytest("-s") for test in ["test_browser"]: reprec.stdout.fnmatch_lines(["*Finalized*"]) - def test_class_scope_with_normal_tests(self, testdir): - testpath = testdir.makepyfile( + def test_class_scope_with_normal_tests(self, pytester: Pytester) -> None: + testpath = pytester.makepyfile( """ import pytest @@ -2849,12 +2899,12 @@ class Test2(object): def test_c(self, a): assert a == 3""" ) - reprec = testdir.inline_run(testpath) + reprec = pytester.inline_run(testpath) for test in ["test_a", "test_b", "test_c"]: assert reprec.matchreport(test).passed - def test_request_is_clean(self, testdir): - testdir.makepyfile( + def test_request_is_clean(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest values = [] @@ -2865,12 +2915,12 @@ def test_fix(fix): pass """ ) - reprec = testdir.inline_run("-s") + reprec = pytester.inline_run("-s") values = reprec.getcalls("pytest_runtest_call")[0].item.module.values assert values == [1, 2] - def test_parametrize_separated_lifecycle(self, testdir): - testdir.makepyfile( + def test_parametrize_separated_lifecycle(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -2886,7 +2936,7 @@ def test_2(arg): values.append(arg) """ ) - reprec = testdir.inline_run("-vs") + reprec = pytester.inline_run("-vs") reprec.assertoutcome(passed=4) values = reprec.getcalls("pytest_runtest_call")[0].item.module.values import pprint @@ -2898,8 +2948,10 @@ def test_2(arg): assert values[3] == values[4] == 2 assert values[5] == "fin2" - def test_parametrize_function_scoped_finalizers_called(self, testdir): - testdir.makepyfile( + def test_parametrize_function_scoped_finalizers_called( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( """ import pytest @@ -2919,13 +2971,15 @@ def test_3(): assert values == [1, "fin1", 2, "fin2", 1, "fin1", 2, "fin2"] """ ) - reprec = testdir.inline_run("-v") + reprec = pytester.inline_run("-v") reprec.assertoutcome(passed=5) @pytest.mark.parametrize("scope", ["session", "function", "module"]) - def test_finalizer_order_on_parametrization(self, scope, testdir): + def test_finalizer_order_on_parametrization( + self, scope, pytester: Pytester + ) -> None: """#246""" - testdir.makepyfile( + pytester.makepyfile( """ import pytest values = [] @@ -2956,12 +3010,12 @@ def test_other(): """ % {"scope": scope} ) - reprec = testdir.inline_run("-lvs") + reprec = pytester.inline_run("-lvs") reprec.assertoutcome(passed=3) - def test_class_scope_parametrization_ordering(self, testdir): + def test_class_scope_parametrization_ordering(self, pytester: Pytester) -> None: """#396""" - testdir.makepyfile( + pytester.makepyfile( """ import pytest values = [] @@ -2982,7 +3036,7 @@ def test_population(self, human): values.append("test_population") """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=6) values = reprec.getcalls("pytest_runtest_call")[0].item.module.values assert values == [ @@ -2998,8 +3052,8 @@ def test_population(self, human): "fin Doe", ] - def test_parametrize_setup_function(self, testdir): - testdir.makepyfile( + def test_parametrize_setup_function(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -3028,11 +3082,13 @@ def test_3(): """ ) - reprec = testdir.inline_run("-v") + reprec = pytester.inline_run("-v") reprec.assertoutcome(passed=6) - def test_fixture_marked_function_not_collected_as_test(self, testdir): - testdir.makepyfile( + def test_fixture_marked_function_not_collected_as_test( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( """ import pytest @pytest.fixture @@ -3043,11 +3099,11 @@ def test_something(test_app): assert test_app == 1 """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=1) - def test_params_and_ids(self, testdir): - testdir.makepyfile( + def test_params_and_ids(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -3060,11 +3116,11 @@ def test_foo(fix): assert 1 """ ) - res = testdir.runpytest("-v") + res = pytester.runpytest("-v") res.stdout.fnmatch_lines(["*test_foo*alpha*", "*test_foo*beta*"]) - def test_params_and_ids_yieldfixture(self, testdir): - testdir.makepyfile( + def test_params_and_ids_yieldfixture(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -3076,12 +3132,14 @@ def test_foo(fix): assert 1 """ ) - res = testdir.runpytest("-v") + res = pytester.runpytest("-v") res.stdout.fnmatch_lines(["*test_foo*alpha*", "*test_foo*beta*"]) - def test_deterministic_fixture_collection(self, testdir, monkeypatch): + def test_deterministic_fixture_collection( + self, pytester: Pytester, monkeypatch + ) -> None: """#920""" - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -3106,21 +3164,21 @@ def test_foo(B): """ ) monkeypatch.setenv("PYTHONHASHSEED", "1") - out1 = testdir.runpytest_subprocess("-v") + out1 = pytester.runpytest_subprocess("-v") monkeypatch.setenv("PYTHONHASHSEED", "2") - out2 = testdir.runpytest_subprocess("-v") - out1 = [ + out2 = pytester.runpytest_subprocess("-v") + output1 = [ line for line in out1.outlines if line.startswith("test_deterministic_fixture_collection.py::test_foo") ] - out2 = [ + output2 = [ line for line in out2.outlines if line.startswith("test_deterministic_fixture_collection.py::test_foo") ] - assert len(out1) == 12 - assert out1 == out2 + assert len(output1) == 12 + assert output1 == output2 class TestRequestScopeAccess: @@ -3134,8 +3192,8 @@ class TestRequestScopeAccess: ], ) - def test_setup(self, testdir, scope, ok, error): - testdir.makepyfile( + def test_setup(self, pytester: Pytester, scope, ok, error) -> None: + pytester.makepyfile( """ import pytest @pytest.fixture(scope=%r, autouse=True) @@ -3152,11 +3210,11 @@ def test_func(): """ % (scope, ok.split(), error.split()) ) - reprec = testdir.inline_run("-l") + reprec = pytester.inline_run("-l") reprec.assertoutcome(passed=1) - def test_funcarg(self, testdir, scope, ok, error): - testdir.makepyfile( + def test_funcarg(self, pytester: Pytester, scope, ok, error) -> None: + pytester.makepyfile( """ import pytest @pytest.fixture(scope=%r) @@ -3173,13 +3231,13 @@ def test_func(arg): """ % (scope, ok.split(), error.split()) ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=1) class TestErrors: - def test_subfactory_missing_funcarg(self, testdir): - testdir.makepyfile( + def test_subfactory_missing_funcarg(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @pytest.fixture() @@ -3189,14 +3247,14 @@ def test_something(gen): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() assert result.ret != 0 result.stdout.fnmatch_lines( ["*def gen(qwe123):*", "*fixture*qwe123*not found*", "*1 error*"] ) - def test_issue498_fixture_finalizer_failing(self, testdir): - testdir.makepyfile( + def test_issue498_fixture_finalizer_failing(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @pytest.fixture @@ -3215,7 +3273,7 @@ def test_3(): assert values[0] != values[1] """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( """ *ERROR*teardown*test_1* @@ -3226,8 +3284,8 @@ def test_3(): """ ) - def test_setupfunc_missing_funcarg(self, testdir): - testdir.makepyfile( + def test_setupfunc_missing_funcarg(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @pytest.fixture(autouse=True) @@ -3237,7 +3295,7 @@ def test_something(): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() assert result.ret != 0 result.stdout.fnmatch_lines( ["*def gen(qwe123):*", "*fixture*qwe123*not found*", "*1 error*"] @@ -3245,12 +3303,12 @@ def test_something(): class TestShowFixtures: - def test_funcarg_compat(self, testdir): - config = testdir.parseconfigure("--funcargs") + def test_funcarg_compat(self, pytester: Pytester) -> None: + config = pytester.parseconfigure("--funcargs") assert config.option.showfixtures - def test_show_fixtures(self, testdir): - result = testdir.runpytest("--fixtures") + def test_show_fixtures(self, pytester: Pytester) -> None: + result = pytester.runpytest("--fixtures") result.stdout.fnmatch_lines( [ "tmpdir_factory [[]session scope[]]", @@ -3260,8 +3318,8 @@ def test_show_fixtures(self, testdir): ] ) - def test_show_fixtures_verbose(self, testdir): - result = testdir.runpytest("--fixtures", "-v") + def test_show_fixtures_verbose(self, pytester: Pytester) -> None: + result = pytester.runpytest("--fixtures", "-v") result.stdout.fnmatch_lines( [ "tmpdir_factory [[]session scope[]] -- *tmpdir.py*", @@ -3271,8 +3329,8 @@ def test_show_fixtures_verbose(self, testdir): ] ) - def test_show_fixtures_testmodule(self, testdir): - p = testdir.makepyfile( + def test_show_fixtures_testmodule(self, pytester: Pytester) -> None: + p = pytester.makepyfile( ''' import pytest @pytest.fixture @@ -3283,7 +3341,7 @@ def arg1(): """ hello world """ ''' ) - result = testdir.runpytest("--fixtures", p) + result = pytester.runpytest("--fixtures", p) result.stdout.fnmatch_lines( """ *tmpdir @@ -3295,8 +3353,8 @@ def arg1(): result.stdout.no_fnmatch_line("*arg0*") @pytest.mark.parametrize("testmod", [True, False]) - def test_show_fixtures_conftest(self, testdir, testmod): - testdir.makeconftest( + def test_show_fixtures_conftest(self, pytester: Pytester, testmod) -> None: + pytester.makeconftest( ''' import pytest @pytest.fixture @@ -3305,13 +3363,13 @@ def arg1(): ''' ) if testmod: - testdir.makepyfile( + pytester.makepyfile( """ def test_hello(): pass """ ) - result = testdir.runpytest("--fixtures") + result = pytester.runpytest("--fixtures") result.stdout.fnmatch_lines( """ *tmpdir* @@ -3321,8 +3379,8 @@ def test_hello(): """ ) - def test_show_fixtures_trimmed_doc(self, testdir): - p = testdir.makepyfile( + def test_show_fixtures_trimmed_doc(self, pytester: Pytester) -> None: + p = pytester.makepyfile( textwrap.dedent( '''\ import pytest @@ -3343,7 +3401,7 @@ def arg2(): ''' ) ) - result = testdir.runpytest("--fixtures", p) + result = pytester.runpytest("--fixtures", p) result.stdout.fnmatch_lines( textwrap.dedent( """\ @@ -3358,8 +3416,8 @@ def arg2(): ) ) - def test_show_fixtures_indented_doc(self, testdir): - p = testdir.makepyfile( + def test_show_fixtures_indented_doc(self, pytester: Pytester) -> None: + p = pytester.makepyfile( textwrap.dedent( '''\ import pytest @@ -3372,7 +3430,7 @@ def fixture1(): ''' ) ) - result = testdir.runpytest("--fixtures", p) + result = pytester.runpytest("--fixtures", p) result.stdout.fnmatch_lines( textwrap.dedent( """\ @@ -3384,8 +3442,10 @@ def fixture1(): ) ) - def test_show_fixtures_indented_doc_first_line_unindented(self, testdir): - p = testdir.makepyfile( + def test_show_fixtures_indented_doc_first_line_unindented( + self, pytester: Pytester + ) -> None: + p = pytester.makepyfile( textwrap.dedent( '''\ import pytest @@ -3398,7 +3458,7 @@ def fixture1(): ''' ) ) - result = testdir.runpytest("--fixtures", p) + result = pytester.runpytest("--fixtures", p) result.stdout.fnmatch_lines( textwrap.dedent( """\ @@ -3411,8 +3471,8 @@ def fixture1(): ) ) - def test_show_fixtures_indented_in_class(self, testdir): - p = testdir.makepyfile( + def test_show_fixtures_indented_in_class(self, pytester: Pytester) -> None: + p = pytester.makepyfile( textwrap.dedent( '''\ import pytest @@ -3426,7 +3486,7 @@ def fixture1(self): ''' ) ) - result = testdir.runpytest("--fixtures", p) + result = pytester.runpytest("--fixtures", p) result.stdout.fnmatch_lines( textwrap.dedent( """\ @@ -3439,9 +3499,9 @@ def fixture1(self): ) ) - def test_show_fixtures_different_files(self, testdir): + def test_show_fixtures_different_files(self, pytester: Pytester) -> None: """`--fixtures` only shows fixtures from first file (#833).""" - testdir.makepyfile( + pytester.makepyfile( test_a=''' import pytest @@ -3454,7 +3514,7 @@ def test_a(fix_a): pass ''' ) - testdir.makepyfile( + pytester.makepyfile( test_b=''' import pytest @@ -3467,7 +3527,7 @@ def test_b(fix_b): pass ''' ) - result = testdir.runpytest("--fixtures") + result = pytester.runpytest("--fixtures") result.stdout.fnmatch_lines( """ * fixtures defined from test_a * @@ -3480,8 +3540,8 @@ def test_b(fix_b): """ ) - def test_show_fixtures_with_same_name(self, testdir): - testdir.makeconftest( + def test_show_fixtures_with_same_name(self, pytester: Pytester) -> None: + pytester.makeconftest( ''' import pytest @pytest.fixture @@ -3490,13 +3550,13 @@ def arg1(): return "Hello World" ''' ) - testdir.makepyfile( + pytester.makepyfile( """ def test_foo(arg1): assert arg1 == "Hello World" """ ) - testdir.makepyfile( + pytester.makepyfile( ''' import pytest @pytest.fixture @@ -3507,7 +3567,7 @@ def test_bar(arg1): assert arg1 == "Hi" ''' ) - result = testdir.runpytest("--fixtures") + result = pytester.runpytest("--fixtures") result.stdout.fnmatch_lines( """ * fixtures defined from conftest * @@ -3531,8 +3591,8 @@ def foo(): class TestContextManagerFixtureFuncs: - def test_simple(self, testdir: Testdir) -> None: - testdir.makepyfile( + def test_simple(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @pytest.fixture @@ -3547,7 +3607,7 @@ def test_2(arg1): assert 0 """ ) - result = testdir.runpytest("-s") + result = pytester.runpytest("-s") result.stdout.fnmatch_lines( """ *setup* @@ -3559,8 +3619,8 @@ def test_2(arg1): """ ) - def test_scoped(self, testdir: Testdir) -> None: - testdir.makepyfile( + def test_scoped(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @pytest.fixture(scope="module") @@ -3574,7 +3634,7 @@ def test_2(arg1): print("test2", arg1) """ ) - result = testdir.runpytest("-s") + result = pytester.runpytest("-s") result.stdout.fnmatch_lines( """ *setup* @@ -3584,8 +3644,8 @@ def test_2(arg1): """ ) - def test_setup_exception(self, testdir: Testdir) -> None: - testdir.makepyfile( + def test_setup_exception(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @pytest.fixture(scope="module") @@ -3596,7 +3656,7 @@ def test_1(arg1): pass """ ) - result = testdir.runpytest("-s") + result = pytester.runpytest("-s") result.stdout.fnmatch_lines( """ *pytest.fail*setup* @@ -3604,8 +3664,8 @@ def test_1(arg1): """ ) - def test_teardown_exception(self, testdir: Testdir) -> None: - testdir.makepyfile( + def test_teardown_exception(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @pytest.fixture(scope="module") @@ -3616,7 +3676,7 @@ def test_1(arg1): pass """ ) - result = testdir.runpytest("-s") + result = pytester.runpytest("-s") result.stdout.fnmatch_lines( """ *pytest.fail*teardown* @@ -3624,8 +3684,8 @@ def test_1(arg1): """ ) - def test_yields_more_than_one(self, testdir: Testdir) -> None: - testdir.makepyfile( + def test_yields_more_than_one(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @pytest.fixture(scope="module") @@ -3636,7 +3696,7 @@ def test_1(arg1): pass """ ) - result = testdir.runpytest("-s") + result = pytester.runpytest("-s") result.stdout.fnmatch_lines( """ *fixture function* @@ -3644,8 +3704,8 @@ def test_1(arg1): """ ) - def test_custom_name(self, testdir: Testdir) -> None: - testdir.makepyfile( + def test_custom_name(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @pytest.fixture(name='meow') @@ -3655,13 +3715,13 @@ def test_1(meow): print(meow) """ ) - result = testdir.runpytest("-s") + result = pytester.runpytest("-s") result.stdout.fnmatch_lines(["*mew*"]) class TestParameterizedSubRequest: - def test_call_from_fixture(self, testdir): - testdir.makepyfile( + def test_call_from_fixture(self, pytester: Pytester) -> None: + pytester.makepyfile( test_call_from_fixture=""" import pytest @@ -3677,7 +3737,7 @@ def test_foo(request, get_named_fixture): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( [ "The requested fixture has no parameter defined for test:", @@ -3690,8 +3750,8 @@ def test_foo(request, get_named_fixture): ] ) - def test_call_from_test(self, testdir): - testdir.makepyfile( + def test_call_from_test(self, pytester: Pytester) -> None: + pytester.makepyfile( test_call_from_test=""" import pytest @@ -3703,7 +3763,7 @@ def test_foo(request): request.getfixturevalue('fix_with_param') """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( [ "The requested fixture has no parameter defined for test:", @@ -3716,8 +3776,8 @@ def test_foo(request): ] ) - def test_external_fixture(self, testdir): - testdir.makeconftest( + def test_external_fixture(self, pytester: Pytester) -> None: + pytester.makeconftest( """ import pytest @@ -3727,13 +3787,13 @@ def fix_with_param(request): """ ) - testdir.makepyfile( + pytester.makepyfile( test_external_fixture=""" def test_foo(request): request.getfixturevalue('fix_with_param') """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( [ "The requested fixture has no parameter defined for test:", @@ -3747,11 +3807,11 @@ def test_foo(request): ] ) - def test_non_relative_path(self, testdir): - tests_dir = testdir.mkdir("tests") - fixdir = testdir.mkdir("fixtures") - fixfile = fixdir.join("fix.py") - fixfile.write( + def test_non_relative_path(self, pytester: Pytester) -> None: + tests_dir = pytester.mkdir("tests") + fixdir = pytester.mkdir("fixtures") + fixfile = fixdir.joinpath("fix.py") + fixfile.write_text( textwrap.dedent( """\ import pytest @@ -3763,8 +3823,8 @@ def fix_with_param(request): ) ) - testfile = tests_dir.join("test_foos.py") - testfile.write( + testfile = tests_dir.joinpath("test_foos.py") + testfile.write_text( textwrap.dedent( """\ from fix import fix_with_param @@ -3775,9 +3835,9 @@ def test_foo(request): ) ) - tests_dir.chdir() - testdir.syspathinsert(fixdir) - result = testdir.runpytest() + os.chdir(tests_dir) + pytester.syspathinsert(fixdir) + result = pytester.runpytest() result.stdout.fnmatch_lines( [ "The requested fixture has no parameter defined for test:", @@ -3792,9 +3852,9 @@ def test_foo(request): ) # With non-overlapping rootdir, passing tests_dir. - rootdir = testdir.mkdir("rootdir") - rootdir.chdir() - result = testdir.runpytest("--rootdir", rootdir, tests_dir) + rootdir = pytester.mkdir("rootdir") + os.chdir(rootdir) + result = pytester.runpytest("--rootdir", rootdir, tests_dir) result.stdout.fnmatch_lines( [ "The requested fixture has no parameter defined for test:", @@ -3809,8 +3869,8 @@ def test_foo(request): ) -def test_pytest_fixture_setup_and_post_finalizer_hook(testdir): - testdir.makeconftest( +def test_pytest_fixture_setup_and_post_finalizer_hook(pytester: Pytester) -> None: + pytester.makeconftest( """ def pytest_fixture_setup(fixturedef, request): print('ROOT setup hook called for {0} from {1}'.format(fixturedef.argname, request.node.name)) @@ -3818,7 +3878,7 @@ def pytest_fixture_post_finalizer(fixturedef, request): print('ROOT finalizer hook called for {0} from {1}'.format(fixturedef.argname, request.node.name)) """ ) - testdir.makepyfile( + pytester.makepyfile( **{ "tests/conftest.py": """ def pytest_fixture_setup(fixturedef, request): @@ -3839,7 +3899,7 @@ def test_func(my_fixture): """, } ) - result = testdir.runpytest("-s") + result = pytester.runpytest("-s") assert result.ret == 0 result.stdout.fnmatch_lines( [ @@ -3856,10 +3916,12 @@ class TestScopeOrdering: """Class of tests that ensure fixtures are ordered based on their scopes (#2405)""" @pytest.mark.parametrize("variant", ["mark", "autouse"]) - def test_func_closure_module_auto(self, testdir, variant, monkeypatch): + def test_func_closure_module_auto( + self, pytester: Pytester, variant, monkeypatch + ) -> None: """Semantically identical to the example posted in #2405 when ``use_mark=True``""" monkeypatch.setenv("FIXTURE_ACTIVATION_VARIANT", variant) - testdir.makepyfile( + pytester.makepyfile( """ import warnings import os @@ -3885,16 +3947,18 @@ def test_func(m1): pass """ ) - items, _ = testdir.inline_genitems() + items, _ = pytester.inline_genitems() request = FixtureRequest(items[0], _ispytest=True) assert request.fixturenames == "m1 f1".split() - def test_func_closure_with_native_fixtures(self, testdir, monkeypatch) -> None: + def test_func_closure_with_native_fixtures( + self, pytester: Pytester, monkeypatch: MonkeyPatch + ) -> None: """Sanity check that verifies the order returned by the closures and the actual fixture execution order: The execution order may differ because of fixture inter-dependencies. """ monkeypatch.setattr(pytest, "FIXTURE_ORDER", [], raising=False) - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -3931,19 +3995,19 @@ def f2(): def test_foo(f1, p1, m1, f2, s1): pass """ ) - items, _ = testdir.inline_genitems() + items, _ = pytester.inline_genitems() request = FixtureRequest(items[0], _ispytest=True) # order of fixtures based on their scope and position in the parameter list assert ( request.fixturenames == "s1 my_tmpdir_factory p1 m1 f1 f2 my_tmpdir".split() ) - testdir.runpytest() + pytester.runpytest() # actual fixture execution differs: dependent fixtures must be created first ("my_tmpdir") FIXTURE_ORDER = pytest.FIXTURE_ORDER # type: ignore[attr-defined] assert FIXTURE_ORDER == "s1 my_tmpdir_factory p1 m1 my_tmpdir f1 f2".split() - def test_func_closure_module(self, testdir): - testdir.makepyfile( + def test_func_closure_module(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -3957,15 +4021,15 @@ def test_func(f1, m1): pass """ ) - items, _ = testdir.inline_genitems() + items, _ = pytester.inline_genitems() request = FixtureRequest(items[0], _ispytest=True) assert request.fixturenames == "m1 f1".split() - def test_func_closure_scopes_reordered(self, testdir): + def test_func_closure_scopes_reordered(self, pytester: Pytester) -> None: """Test ensures that fixtures are ordered by scope regardless of the order of the parameters, although fixtures of same scope keep the declared order """ - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -3990,13 +4054,15 @@ def test_func(self, f2, f1, c1, m1, s1): pass """ ) - items, _ = testdir.inline_genitems() + items, _ = pytester.inline_genitems() request = FixtureRequest(items[0], _ispytest=True) assert request.fixturenames == "s1 m1 c1 f2 f1".split() - def test_func_closure_same_scope_closer_root_first(self, testdir): + def test_func_closure_same_scope_closer_root_first( + self, pytester: Pytester + ) -> None: """Auto-use fixtures of same scope are ordered by closer-to-root first""" - testdir.makeconftest( + pytester.makeconftest( """ import pytest @@ -4004,7 +4070,7 @@ def test_func_closure_same_scope_closer_root_first(self, testdir): def m_conf(): pass """ ) - testdir.makepyfile( + pytester.makepyfile( **{ "sub/conftest.py": """ import pytest @@ -4030,13 +4096,13 @@ def test_func(m_test, f1): """, } ) - items, _ = testdir.inline_genitems() + items, _ = pytester.inline_genitems() request = FixtureRequest(items[0], _ispytest=True) assert request.fixturenames == "p_sub m_conf m_sub m_test f1".split() - def test_func_closure_all_scopes_complex(self, testdir): + def test_func_closure_all_scopes_complex(self, pytester: Pytester) -> None: """Complex test involving all scopes and mixing autouse with normal fixtures""" - testdir.makeconftest( + pytester.makeconftest( """ import pytest @@ -4047,8 +4113,8 @@ def s1(): pass def p1(): pass """ ) - testdir.makepyfile(**{"__init__.py": ""}) - testdir.makepyfile( + pytester.makepyfile(**{"__init__.py": ""}) + pytester.makepyfile( """ import pytest @@ -4074,11 +4140,11 @@ def test_func(self, f2, f1, m2): pass """ ) - items, _ = testdir.inline_genitems() + items, _ = pytester.inline_genitems() request = FixtureRequest(items[0], _ispytest=True) assert request.fixturenames == "s1 p1 m1 m2 c1 f2 f1".split() - def test_multiple_packages(self, testdir): + def test_multiple_packages(self, pytester: Pytester) -> None: """Complex test involving multiple package fixtures. Make sure teardowns are executed in order. . @@ -4093,11 +4159,12 @@ def test_multiple_packages(self, testdir): ├── conftest.py └── test_2.py """ - root = testdir.mkdir("root") - root.join("__init__.py").write("values = []") - sub1 = root.mkdir("sub1") - sub1.ensure("__init__.py") - sub1.join("conftest.py").write( + root = pytester.mkdir("root") + root.joinpath("__init__.py").write_text("values = []") + sub1 = root.joinpath("sub1") + sub1.mkdir() + sub1.joinpath("__init__.py").touch() + sub1.joinpath("conftest.py").write_text( textwrap.dedent( """\ import pytest @@ -4110,7 +4177,7 @@ def fix(): """ ) ) - sub1.join("test_1.py").write( + sub1.joinpath("test_1.py").write_text( textwrap.dedent( """\ from .. import values @@ -4119,9 +4186,10 @@ def test_1(fix): """ ) ) - sub2 = root.mkdir("sub2") - sub2.ensure("__init__.py") - sub2.join("conftest.py").write( + sub2 = root.joinpath("sub2") + sub2.mkdir() + sub2.joinpath("__init__.py").touch() + sub2.joinpath("conftest.py").write_text( textwrap.dedent( """\ import pytest @@ -4134,7 +4202,7 @@ def fix(): """ ) ) - sub2.join("test_2.py").write( + sub2.joinpath("test_2.py").write_text( textwrap.dedent( """\ from .. import values @@ -4143,14 +4211,14 @@ def test_2(fix): """ ) ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=2) - def test_class_fixture_self_instance(self, testdir): + def test_class_fixture_self_instance(self, pytester: Pytester) -> None: """Check that plugin classes which implement fixtures receive the plugin instance as self (see #2270). """ - testdir.makeconftest( + pytester.makeconftest( """ import pytest @@ -4168,14 +4236,14 @@ def myfix(self): """ ) - testdir.makepyfile( + pytester.makepyfile( """ class TestClass(object): def test_1(self, myfix): assert myfix == 1 """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=1) @@ -4190,9 +4258,9 @@ def fix(): assert fix() == 1 -def test_fixture_param_shadowing(testdir): +def test_fixture_param_shadowing(pytester: Pytester) -> None: """Parametrized arguments would be shadowed if a fixture with the same name also exists (#5036)""" - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -4225,7 +4293,7 @@ def test_indirect(arg2): """ ) # Only one test should have run - result = testdir.runpytest("-v") + result = pytester.runpytest("-v") result.assert_outcomes(passed=4) result.stdout.fnmatch_lines(["*::test_direct[[]1[]]*"]) result.stdout.fnmatch_lines(["*::test_normal_fixture[[]a[]]*"]) @@ -4233,9 +4301,9 @@ def test_indirect(arg2): result.stdout.fnmatch_lines(["*::test_indirect[[]1[]]*"]) -def test_fixture_named_request(testdir): - testdir.copy_example("fixtures/test_fixture_named_request.py") - result = testdir.runpytest() +def test_fixture_named_request(pytester: Pytester) -> None: + pytester.copy_example("fixtures/test_fixture_named_request.py") + result = pytester.runpytest() result.stdout.fnmatch_lines( [ "*'request' is a reserved word for fixtures, use another name:", @@ -4244,9 +4312,9 @@ def test_fixture_named_request(testdir): ) -def test_indirect_fixture_does_not_break_scope(testdir): +def test_indirect_fixture_does_not_break_scope(pytester: Pytester) -> None: """Ensure that fixture scope is respected when using indirect fixtures (#570)""" - testdir.makepyfile( + pytester.makepyfile( """ import pytest instantiated = [] @@ -4291,14 +4359,14 @@ def test_check_fixture_instantiations(): ] """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.assert_outcomes(passed=7) -def test_fixture_parametrization_nparray(testdir): +def test_fixture_parametrization_nparray(pytester: Pytester) -> None: pytest.importorskip("numpy") - testdir.makepyfile( + pytester.makepyfile( """ from numpy import linspace from pytest import fixture @@ -4311,18 +4379,18 @@ def test_bug(value): assert value == value """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.assert_outcomes(passed=10) -def test_fixture_arg_ordering(testdir): +def test_fixture_arg_ordering(pytester: Pytester) -> None: """ This test describes how fixtures in the same scope but without explicit dependencies between them are created. While users should make dependencies explicit, often they rely on this order, so this test exists to catch regressions in this regard. See #6540 and #6492. """ - p1 = testdir.makepyfile( + p1 = pytester.makepyfile( """ import pytest @@ -4346,12 +4414,12 @@ def test_suffix(fix_combined): assert suffixes == ["fix_1", "fix_2", "fix_3", "fix_4", "fix_5"] """ ) - result = testdir.runpytest("-vv", str(p1)) + result = pytester.runpytest("-vv", str(p1)) assert result.ret == 0 -def test_yield_fixture_with_no_value(testdir): - testdir.makepyfile( +def test_yield_fixture_with_no_value(pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @pytest.fixture(name='custom') @@ -4364,7 +4432,7 @@ def test_fixt(custom): """ ) expected = "E ValueError: custom did not yield a value" - result = testdir.runpytest() + result = pytester.runpytest() result.assert_outcomes(errors=1) result.stdout.fnmatch_lines([expected]) assert result.ret == ExitCode.TESTS_FAILED diff --git a/testing/python/integration.py b/testing/python/integration.py index f006e5ed4ee..5dce6bdca28 100644 --- a/testing/python/integration.py +++ b/testing/python/integration.py @@ -3,13 +3,14 @@ import pytest from _pytest import runner from _pytest._code import getfslineno +from _pytest.pytester import Pytester class TestOEJSKITSpecials: def test_funcarg_non_pycollectobj( - self, testdir, recwarn + self, pytester: Pytester, recwarn ) -> None: # rough jstests usage - testdir.makeconftest( + pytester.makeconftest( """ import pytest def pytest_pycollect_makeitem(collector, name, obj): @@ -20,7 +21,7 @@ def reportinfo(self): return self.fspath, 3, "xyz" """ ) - modcol = testdir.getmodulecol( + modcol = pytester.getmodulecol( """ import pytest @pytest.fixture @@ -39,8 +40,10 @@ class MyClass(object): pytest._fillfuncargs(clscol) assert clscol.funcargs["arg1"] == 42 - def test_autouse_fixture(self, testdir, recwarn) -> None: # rough jstests usage - testdir.makeconftest( + def test_autouse_fixture( + self, pytester: Pytester, recwarn + ) -> None: # rough jstests usage + pytester.makeconftest( """ import pytest def pytest_pycollect_makeitem(collector, name, obj): @@ -51,7 +54,7 @@ def reportinfo(self): return self.fspath, 3, "xyz" """ ) - modcol = testdir.getmodulecol( + modcol = pytester.getmodulecol( """ import pytest @pytest.fixture(autouse=True) @@ -125,8 +128,8 @@ def f(x, y, z): values = getfuncargnames(f) assert values == ("y", "z") - def test_unittest_mock(self, testdir): - testdir.makepyfile( + def test_unittest_mock(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import unittest.mock class T(unittest.TestCase): @@ -137,11 +140,11 @@ def test_hello(self, abspath): abspath.assert_any_call("hello") """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=1) - def test_unittest_mock_and_fixture(self, testdir): - testdir.makepyfile( + def test_unittest_mock_and_fixture(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import os.path import unittest.mock @@ -158,12 +161,12 @@ def test_hello(inject_me): os.path.abspath("hello") """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=1) - def test_unittest_mock_and_pypi_mock(self, testdir): + def test_unittest_mock_and_pypi_mock(self, pytester: Pytester) -> None: pytest.importorskip("mock", "1.0.1") - testdir.makepyfile( + pytester.makepyfile( """ import mock import unittest.mock @@ -181,15 +184,15 @@ def test_hello_mock(self, abspath): abspath.assert_any_call("hello") """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=2) - def test_mock_sentinel_check_against_numpy_like(self, testdir): + def test_mock_sentinel_check_against_numpy_like(self, pytester: Pytester) -> None: """Ensure our function that detects mock arguments compares against sentinels using identity to circumvent objects which can't be compared with equality against others in a truth context, like with numpy arrays (#5606). """ - testdir.makepyfile( + pytester.makepyfile( dummy=""" class NumpyLike: def __init__(self, value): @@ -199,7 +202,7 @@ def __eq__(self, other): FOO = NumpyLike(10) """ ) - testdir.makepyfile( + pytester.makepyfile( """ from unittest.mock import patch import dummy @@ -209,12 +212,12 @@ def test_hello(self): assert dummy.FOO.value == 50 """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=1) - def test_mock(self, testdir): + def test_mock(self, pytester: Pytester) -> None: pytest.importorskip("mock", "1.0.1") - testdir.makepyfile( + pytester.makepyfile( """ import os import unittest @@ -237,7 +240,7 @@ def test_someting(normpath, abspath, tmpdir): assert os.path.basename("123") == "mock_basename" """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=2) calls = reprec.getcalls("pytest_runtest_logreport") funcnames = [ @@ -245,9 +248,9 @@ def test_someting(normpath, abspath, tmpdir): ] assert funcnames == ["T.test_hello", "test_someting"] - def test_mock_sorting(self, testdir): + def test_mock_sorting(self, pytester: Pytester) -> None: pytest.importorskip("mock", "1.0.1") - testdir.makepyfile( + pytester.makepyfile( """ import os import mock @@ -263,15 +266,15 @@ def test_three(abspath): pass """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() calls = reprec.getreports("pytest_runtest_logreport") calls = [x for x in calls if x.when == "call"] names = [x.nodeid.split("::")[-1] for x in calls] assert names == ["test_one", "test_two", "test_three"] - def test_mock_double_patch_issue473(self, testdir): + def test_mock_double_patch_issue473(self, pytester: Pytester) -> None: pytest.importorskip("mock", "1.0.1") - testdir.makepyfile( + pytester.makepyfile( """ from mock import patch from pytest import mark @@ -284,13 +287,13 @@ def test_simple_thing(self, mock_path, mock_getcwd): pass """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=1) class TestReRunTests: - def test_rerun(self, testdir): - testdir.makeconftest( + def test_rerun(self, pytester: Pytester) -> None: + pytester.makeconftest( """ from _pytest.runner import runtestprotocol def pytest_runtest_protocol(item, nextitem): @@ -298,7 +301,7 @@ def pytest_runtest_protocol(item, nextitem): runtestprotocol(item, log=True, nextitem=nextitem) """ ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest count = 0 @@ -314,7 +317,7 @@ def test_fix(fix): pass """ ) - result = testdir.runpytest("-s") + result = pytester.runpytest("-s") result.stdout.fnmatch_lines( """ *fix count 0* @@ -336,21 +339,21 @@ def test_pytestconfig_is_session_scoped() -> None: class TestNoselikeTestAttribute: - def test_module_with_global_test(self, testdir): - testdir.makepyfile( + def test_module_with_global_test(self, pytester: Pytester) -> None: + pytester.makepyfile( """ __test__ = False def test_hello(): pass """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() assert not reprec.getfailedcollections() calls = reprec.getreports("pytest_runtest_logreport") assert not calls - def test_class_and_method(self, testdir): - testdir.makepyfile( + def test_class_and_method(self, pytester: Pytester) -> None: + pytester.makepyfile( """ __test__ = True def test_func(): @@ -363,13 +366,13 @@ def test_method(self): pass """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() assert not reprec.getfailedcollections() calls = reprec.getreports("pytest_runtest_logreport") assert not calls - def test_unittest_class(self, testdir): - testdir.makepyfile( + def test_unittest_class(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import unittest class TC(unittest.TestCase): @@ -381,20 +384,20 @@ def test_2(self): pass """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() assert not reprec.getfailedcollections() call = reprec.getcalls("pytest_collection_modifyitems")[0] assert len(call.items) == 1 assert call.items[0].cls.__name__ == "TC" - def test_class_with_nasty_getattr(self, testdir): + def test_class_with_nasty_getattr(self, pytester: Pytester) -> None: """Make sure we handle classes with a custom nasty __getattr__ right. With a custom __getattr__ which e.g. returns a function (like with a RPC wrapper), we shouldn't assume this meant "__test__ = True". """ # https://github.com/pytest-dev/pytest/issues/1204 - testdir.makepyfile( + pytester.makepyfile( """ class MetaModel(type): @@ -413,7 +416,7 @@ def test_blah(self): pass """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() assert not reprec.getfailedcollections() call = reprec.getcalls("pytest_collection_modifyitems")[0] assert not call.items @@ -422,8 +425,8 @@ def test_blah(self): class TestParameterize: """#351""" - def test_idfn_marker(self, testdir): - testdir.makepyfile( + def test_idfn_marker(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -440,11 +443,11 @@ def test_params(a, b): pass """ ) - res = testdir.runpytest("--collect-only") + res = pytester.runpytest("--collect-only") res.stdout.fnmatch_lines(["*spam-2*", "*ham-2*"]) - def test_idfn_fixture(self, testdir): - testdir.makepyfile( + def test_idfn_fixture(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -468,5 +471,5 @@ def test_params(a, b): pass """ ) - res = testdir.runpytest("--collect-only") + res = pytester.runpytest("--collect-only") res.stdout.fnmatch_lines(["*spam-2*", "*ham-2*"]) diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index 676f1d988bc..c50ea53d255 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -23,7 +23,7 @@ from _pytest.compat import getfuncargnames from _pytest.compat import NOTSET from _pytest.outcomes import fail -from _pytest.pytester import Testdir +from _pytest.pytester import Pytester from _pytest.python import _idval from _pytest.python import idmaker @@ -123,7 +123,7 @@ def func(x): ): metafunc.parametrize("x", [1], scope="doggy") # type: ignore[arg-type] - def test_parametrize_request_name(self, testdir: Testdir) -> None: + def test_parametrize_request_name(self, pytester: Pytester) -> None: """Show proper error when 'request' is used as a parameter name in parametrize (#6183)""" def func(request): @@ -550,12 +550,12 @@ def getini(self, name): ) assert result == [expected] - def test_parametrize_ids_exception(self, testdir: Testdir) -> None: + def test_parametrize_ids_exception(self, pytester: Pytester) -> None: """ - :param testdir: the instance of Testdir class, a temporary + :param pytester: the instance of Pytester class, a temporary test directory. """ - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -567,7 +567,7 @@ def test_foo(arg): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( [ "*Exception: bad ids", @@ -575,8 +575,8 @@ def test_foo(arg): ] ) - def test_parametrize_ids_returns_non_string(self, testdir: Testdir) -> None: - testdir.makepyfile( + def test_parametrize_ids_returns_non_string(self, pytester: Pytester) -> None: + pytester.makepyfile( """\ import pytest @@ -592,7 +592,7 @@ def test_int(arg): assert arg """ ) - result = testdir.runpytest("-vv", "-s") + result = pytester.runpytest("-vv", "-s") result.stdout.fnmatch_lines( [ "test_parametrize_ids_returns_non_string.py::test[arg0] PASSED", @@ -682,7 +682,7 @@ def func(x, y): ): metafunc.parametrize("x, y", [("a", "b")], indirect={}) # type: ignore[arg-type] - def test_parametrize_indirect_list_functional(self, testdir: Testdir) -> None: + def test_parametrize_indirect_list_functional(self, pytester: Pytester) -> None: """ #714 Test parametrization with 'indirect' parameter applied on @@ -690,10 +690,10 @@ def test_parametrize_indirect_list_functional(self, testdir: Testdir) -> None: be used directly rather than being passed to the fixture y. - :param testdir: the instance of Testdir class, a temporary + :param pytester: the instance of Pytester class, a temporary test directory. """ - testdir.makepyfile( + pytester.makepyfile( """ import pytest @pytest.fixture(scope='function') @@ -708,7 +708,7 @@ def test_simple(x,y): assert len(y) == 1 """ ) - result = testdir.runpytest("-v") + result = pytester.runpytest("-v") result.stdout.fnmatch_lines(["*test_simple*a-b*", "*1 passed*"]) def test_parametrize_indirect_list_error(self) -> None: @@ -722,7 +722,7 @@ def func(x, y): metafunc.parametrize("x, y", [("a", "b")], indirect=["x", "z"]) def test_parametrize_uses_no_fixture_error_indirect_false( - self, testdir: Testdir + self, pytester: Pytester ) -> None: """The 'uses no fixture' error tells the user at collection time that the parametrize data they've set up doesn't correspond to the @@ -731,7 +731,7 @@ def test_parametrize_uses_no_fixture_error_indirect_false( #714 """ - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -740,14 +740,14 @@ def test_simple(x): assert len(x) == 3 """ ) - result = testdir.runpytest("--collect-only") + result = pytester.runpytest("--collect-only") result.stdout.fnmatch_lines(["*uses no argument 'y'*"]) def test_parametrize_uses_no_fixture_error_indirect_true( - self, testdir: Testdir + self, pytester: Pytester ) -> None: """#714""" - testdir.makepyfile( + pytester.makepyfile( """ import pytest @pytest.fixture(scope='function') @@ -762,14 +762,14 @@ def test_simple(x): assert len(x) == 3 """ ) - result = testdir.runpytest("--collect-only") + result = pytester.runpytest("--collect-only") result.stdout.fnmatch_lines(["*uses no fixture 'y'*"]) def test_parametrize_indirect_uses_no_fixture_error_indirect_string( - self, testdir: Testdir + self, pytester: Pytester ) -> None: """#714""" - testdir.makepyfile( + pytester.makepyfile( """ import pytest @pytest.fixture(scope='function') @@ -781,14 +781,14 @@ def test_simple(x): assert len(x) == 3 """ ) - result = testdir.runpytest("--collect-only") + result = pytester.runpytest("--collect-only") result.stdout.fnmatch_lines(["*uses no fixture 'y'*"]) def test_parametrize_indirect_uses_no_fixture_error_indirect_list( - self, testdir: Testdir + self, pytester: Pytester ) -> None: """#714""" - testdir.makepyfile( + pytester.makepyfile( """ import pytest @pytest.fixture(scope='function') @@ -800,12 +800,14 @@ def test_simple(x): assert len(x) == 3 """ ) - result = testdir.runpytest("--collect-only") + result = pytester.runpytest("--collect-only") result.stdout.fnmatch_lines(["*uses no fixture 'y'*"]) - def test_parametrize_argument_not_in_indirect_list(self, testdir: Testdir) -> None: + def test_parametrize_argument_not_in_indirect_list( + self, pytester: Pytester + ) -> None: """#714""" - testdir.makepyfile( + pytester.makepyfile( """ import pytest @pytest.fixture(scope='function') @@ -817,13 +819,13 @@ def test_simple(x): assert len(x) == 3 """ ) - result = testdir.runpytest("--collect-only") + result = pytester.runpytest("--collect-only") result.stdout.fnmatch_lines(["*uses no argument 'y'*"]) def test_parametrize_gives_indicative_error_on_function_with_default_argument( - self, testdir + self, pytester: Pytester ) -> None: - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -832,13 +834,13 @@ def test_simple(x, y=1): assert len(x) == 1 """ ) - result = testdir.runpytest("--collect-only") + result = pytester.runpytest("--collect-only") result.stdout.fnmatch_lines( ["*already takes an argument 'y' with a default value"] ) - def test_parametrize_functional(self, testdir: Testdir) -> None: - testdir.makepyfile( + def test_parametrize_functional(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest def pytest_generate_tests(metafunc): @@ -853,7 +855,7 @@ def test_simple(x,y): assert y == 2 """ ) - result = testdir.runpytest("-v") + result = pytester.runpytest("-v") result.stdout.fnmatch_lines( ["*test_simple*1-2*", "*test_simple*2-2*", "*2 passed*"] ) @@ -884,8 +886,8 @@ def test_parametrize_twoargs(self) -> None: assert metafunc._calls[1].funcargs == dict(x=3, y=4) assert metafunc._calls[1].id == "3-4" - def test_parametrize_multiple_times(self, testdir: Testdir) -> None: - testdir.makepyfile( + def test_parametrize_multiple_times(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest pytestmark = pytest.mark.parametrize("x", [1,2]) @@ -897,12 +899,12 @@ def test_meth(self, x, y): assert 0, x """ ) - result = testdir.runpytest() + result = pytester.runpytest() assert result.ret == 1 result.assert_outcomes(failed=6) - def test_parametrize_CSV(self, testdir: Testdir) -> None: - testdir.makepyfile( + def test_parametrize_CSV(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @pytest.mark.parametrize("x, y,", [(1,2), (2,3)]) @@ -910,11 +912,11 @@ def test_func(x, y): assert x+1 == y """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=2) - def test_parametrize_class_scenarios(self, testdir: Testdir) -> None: - testdir.makepyfile( + def test_parametrize_class_scenarios(self, pytester: Pytester) -> None: + pytester.makepyfile( """ # same as doc/en/example/parametrize scenario example def pytest_generate_tests(metafunc): @@ -941,7 +943,7 @@ def test_3(self, arg, arg2): pass """ ) - result = testdir.runpytest("-v") + result = pytester.runpytest("-v") assert result.ret == 0 result.stdout.fnmatch_lines( """ @@ -978,8 +980,8 @@ def function4(arg1, *args, **kwargs): class TestMetafuncFunctional: - def test_attributes(self, testdir: Testdir) -> None: - p = testdir.makepyfile( + def test_attributes(self, pytester: Pytester) -> None: + p = pytester.makepyfile( """ # assumes that generate/provide runs in the same process import sys, pytest @@ -1005,11 +1007,11 @@ def test_method(self, metafunc, pytestconfig): assert metafunc.cls == TestClass """ ) - result = testdir.runpytest(p, "-v") + result = pytester.runpytest(p, "-v") result.assert_outcomes(passed=2) - def test_two_functions(self, testdir: Testdir) -> None: - p = testdir.makepyfile( + def test_two_functions(self, pytester: Pytester) -> None: + p = pytester.makepyfile( """ def pytest_generate_tests(metafunc): metafunc.parametrize('arg1', [10, 20], ids=['0', '1']) @@ -1021,7 +1023,7 @@ def test_func2(arg1): assert arg1 in (10, 20) """ ) - result = testdir.runpytest("-v", p) + result = pytester.runpytest("-v", p) result.stdout.fnmatch_lines( [ "*test_func1*0*PASS*", @@ -1032,8 +1034,8 @@ def test_func2(arg1): ] ) - def test_noself_in_method(self, testdir: Testdir) -> None: - p = testdir.makepyfile( + def test_noself_in_method(self, pytester: Pytester) -> None: + p = pytester.makepyfile( """ def pytest_generate_tests(metafunc): assert 'xyz' not in metafunc.fixturenames @@ -1043,11 +1045,11 @@ def test_hello(xyz): pass """ ) - result = testdir.runpytest(p) + result = pytester.runpytest(p) result.assert_outcomes(passed=1) - def test_generate_tests_in_class(self, testdir: Testdir) -> None: - p = testdir.makepyfile( + def test_generate_tests_in_class(self, pytester: Pytester) -> None: + p = pytester.makepyfile( """ class TestClass(object): def pytest_generate_tests(self, metafunc): @@ -1057,11 +1059,11 @@ def test_myfunc(self, hello): assert hello == "world" """ ) - result = testdir.runpytest("-v", p) + result = pytester.runpytest("-v", p) result.stdout.fnmatch_lines(["*test_myfunc*hello*PASS*", "*1 passed*"]) - def test_two_functions_not_same_instance(self, testdir: Testdir) -> None: - p = testdir.makepyfile( + def test_two_functions_not_same_instance(self, pytester: Pytester) -> None: + p = pytester.makepyfile( """ def pytest_generate_tests(metafunc): metafunc.parametrize('arg1', [10, 20], ids=["0", "1"]) @@ -1072,13 +1074,13 @@ def test_func(self, arg1): self.x = 1 """ ) - result = testdir.runpytest("-v", p) + result = pytester.runpytest("-v", p) result.stdout.fnmatch_lines( ["*test_func*0*PASS*", "*test_func*1*PASS*", "*2 pass*"] ) - def test_issue28_setup_method_in_generate_tests(self, testdir: Testdir) -> None: - p = testdir.makepyfile( + def test_issue28_setup_method_in_generate_tests(self, pytester: Pytester) -> None: + p = pytester.makepyfile( """ def pytest_generate_tests(metafunc): metafunc.parametrize('arg1', [1]) @@ -1090,11 +1092,11 @@ def setup_method(self, func): self.val = 1 """ ) - result = testdir.runpytest(p) + result = pytester.runpytest(p) result.assert_outcomes(passed=1) - def test_parametrize_functional2(self, testdir: Testdir) -> None: - testdir.makepyfile( + def test_parametrize_functional2(self, pytester: Pytester) -> None: + pytester.makepyfile( """ def pytest_generate_tests(metafunc): metafunc.parametrize("arg1", [1,2]) @@ -1103,13 +1105,13 @@ def test_hello(arg1, arg2): assert 0, (arg1, arg2) """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( ["*(1, 4)*", "*(1, 5)*", "*(2, 4)*", "*(2, 5)*", "*4 failed*"] ) - def test_parametrize_and_inner_getfixturevalue(self, testdir: Testdir) -> None: - p = testdir.makepyfile( + def test_parametrize_and_inner_getfixturevalue(self, pytester: Pytester) -> None: + p = pytester.makepyfile( """ def pytest_generate_tests(metafunc): metafunc.parametrize("arg1", [1], indirect=True) @@ -1129,11 +1131,11 @@ def test_func1(arg1, arg2): assert arg1 == 11 """ ) - result = testdir.runpytest("-v", p) + result = pytester.runpytest("-v", p) result.stdout.fnmatch_lines(["*test_func1*1*PASS*", "*1 passed*"]) - def test_parametrize_on_setup_arg(self, testdir: Testdir) -> None: - p = testdir.makepyfile( + def test_parametrize_on_setup_arg(self, pytester: Pytester) -> None: + p = pytester.makepyfile( """ def pytest_generate_tests(metafunc): assert "arg1" in metafunc.fixturenames @@ -1152,17 +1154,17 @@ def test_func(arg2): assert arg2 == 10 """ ) - result = testdir.runpytest("-v", p) + result = pytester.runpytest("-v", p) result.stdout.fnmatch_lines(["*test_func*1*PASS*", "*1 passed*"]) - def test_parametrize_with_ids(self, testdir: Testdir) -> None: - testdir.makeini( + def test_parametrize_with_ids(self, pytester: Pytester) -> None: + pytester.makeini( """ [pytest] console_output_style=classic """ ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest def pytest_generate_tests(metafunc): @@ -1173,14 +1175,14 @@ def test_function(a, b): assert a == b """ ) - result = testdir.runpytest("-v") + result = pytester.runpytest("-v") assert result.ret == 1 result.stdout.fnmatch_lines_random( ["*test_function*basic*PASSED", "*test_function*advanced*FAILED"] ) - def test_parametrize_without_ids(self, testdir: Testdir) -> None: - testdir.makepyfile( + def test_parametrize_without_ids(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest def pytest_generate_tests(metafunc): @@ -1191,7 +1193,7 @@ def test_function(a, b): assert 1 """ ) - result = testdir.runpytest("-v") + result = pytester.runpytest("-v") result.stdout.fnmatch_lines( """ *test_function*1-b0* @@ -1199,8 +1201,8 @@ def test_function(a, b): """ ) - def test_parametrize_with_None_in_ids(self, testdir: Testdir) -> None: - testdir.makepyfile( + def test_parametrize_with_None_in_ids(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest def pytest_generate_tests(metafunc): @@ -1211,7 +1213,7 @@ def test_function(a, b): assert a == b """ ) - result = testdir.runpytest("-v") + result = pytester.runpytest("-v") assert result.ret == 1 result.stdout.fnmatch_lines_random( [ @@ -1221,9 +1223,9 @@ def test_function(a, b): ] ) - def test_fixture_parametrized_empty_ids(self, testdir: Testdir) -> None: + def test_fixture_parametrized_empty_ids(self, pytester: Pytester) -> None: """Fixtures parametrized with empty ids cause an internal error (#1849).""" - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -1235,12 +1237,12 @@ def test_temp(temp): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["* 1 skipped *"]) - def test_parametrized_empty_ids(self, testdir: Testdir) -> None: + def test_parametrized_empty_ids(self, pytester: Pytester) -> None: """Tests parametrized with empty ids cause an internal error (#1849).""" - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -1249,12 +1251,12 @@ def test_temp(temp): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["* 1 skipped *"]) - def test_parametrized_ids_invalid_type(self, testdir: Testdir) -> None: + def test_parametrized_ids_invalid_type(self, pytester: Pytester) -> None: """Test error with non-strings/non-ints, without generator (#1857).""" - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -1263,7 +1265,7 @@ def test_ids_numbers(x,expected): assert x * 2 == expected """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( [ "In test_ids_numbers: ids must be list of string/float/int/bool," @@ -1272,9 +1274,9 @@ def test_ids_numbers(x,expected): ) def test_parametrize_with_identical_ids_get_unique_names( - self, testdir: Testdir + self, pytester: Pytester ) -> None: - testdir.makepyfile( + pytester.makepyfile( """ import pytest def pytest_generate_tests(metafunc): @@ -1285,7 +1287,7 @@ def test_function(a, b): assert a == b """ ) - result = testdir.runpytest("-v") + result = pytester.runpytest("-v") assert result.ret == 1 result.stdout.fnmatch_lines_random( ["*test_function*a0*PASSED*", "*test_function*a1*FAILED*"] @@ -1293,9 +1295,9 @@ def test_function(a, b): @pytest.mark.parametrize(("scope", "length"), [("module", 2), ("function", 4)]) def test_parametrize_scope_overrides( - self, testdir: Testdir, scope: str, length: int + self, pytester: Pytester, scope: str, length: int ) -> None: - testdir.makepyfile( + pytester.makepyfile( """ import pytest values = [] @@ -1316,11 +1318,11 @@ def test_checklength(): """ % (scope, length) ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=5) - def test_parametrize_issue323(self, testdir: Testdir) -> None: - testdir.makepyfile( + def test_parametrize_issue323(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -1334,11 +1336,11 @@ def test_it2(foo): pass """ ) - reprec = testdir.inline_run("--collect-only") + reprec = pytester.inline_run("--collect-only") assert not reprec.getcalls("pytest_internalerror") - def test_usefixtures_seen_in_generate_tests(self, testdir: Testdir) -> None: - testdir.makepyfile( + def test_usefixtures_seen_in_generate_tests(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest def pytest_generate_tests(metafunc): @@ -1350,13 +1352,13 @@ def test_function(): pass """ ) - reprec = testdir.runpytest() + reprec = pytester.runpytest() reprec.assert_outcomes(passed=1) - def test_generate_tests_only_done_in_subdir(self, testdir: Testdir) -> None: - sub1 = testdir.mkpydir("sub1") - sub2 = testdir.mkpydir("sub2") - sub1.join("conftest.py").write( + def test_generate_tests_only_done_in_subdir(self, pytester: Pytester) -> None: + sub1 = pytester.mkpydir("sub1") + sub2 = pytester.mkpydir("sub2") + sub1.joinpath("conftest.py").write_text( textwrap.dedent( """\ def pytest_generate_tests(metafunc): @@ -1364,7 +1366,7 @@ def pytest_generate_tests(metafunc): """ ) ) - sub2.join("conftest.py").write( + sub2.joinpath("conftest.py").write_text( textwrap.dedent( """\ def pytest_generate_tests(metafunc): @@ -1372,13 +1374,13 @@ def pytest_generate_tests(metafunc): """ ) ) - sub1.join("test_in_sub1.py").write("def test_1(): pass") - sub2.join("test_in_sub2.py").write("def test_2(): pass") - result = testdir.runpytest("--keep-duplicates", "-v", "-s", sub1, sub2, sub1) + sub1.joinpath("test_in_sub1.py").write_text("def test_1(): pass") + sub2.joinpath("test_in_sub2.py").write_text("def test_2(): pass") + result = pytester.runpytest("--keep-duplicates", "-v", "-s", sub1, sub2, sub1) result.assert_outcomes(passed=3) - def test_generate_same_function_names_issue403(self, testdir: Testdir) -> None: - testdir.makepyfile( + def test_generate_same_function_names_issue403(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -1392,12 +1394,12 @@ def test_foo(x): test_y = make_tests() """ ) - reprec = testdir.runpytest() + reprec = pytester.runpytest() reprec.assert_outcomes(passed=4) - def test_parametrize_misspelling(self, testdir: Testdir) -> None: + def test_parametrize_misspelling(self, pytester: Pytester) -> None: """#463""" - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -1406,7 +1408,7 @@ def test_foo(x): pass """ ) - result = testdir.runpytest("--collectonly") + result = pytester.runpytest("--collectonly") result.stdout.fnmatch_lines( [ "collected 0 items / 1 error", @@ -1426,8 +1428,8 @@ class TestMetafuncFunctionalAuto: """Tests related to automatically find out the correct scope for parametrized tests (#1832).""" - def test_parametrize_auto_scope(self, testdir: Testdir) -> None: - testdir.makepyfile( + def test_parametrize_auto_scope(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -1445,11 +1447,11 @@ def test_2(animal): """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["* 3 passed *"]) - def test_parametrize_auto_scope_indirect(self, testdir: Testdir) -> None: - testdir.makepyfile( + def test_parametrize_auto_scope_indirect(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -1468,11 +1470,11 @@ def test_2(animal, echo): assert echo in (1, 2, 3) """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["* 3 passed *"]) - def test_parametrize_auto_scope_override_fixture(self, testdir: Testdir) -> None: - testdir.makepyfile( + def test_parametrize_auto_scope_override_fixture(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -1485,11 +1487,11 @@ def test_1(animal): assert animal in ('dog', 'cat') """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["* 2 passed *"]) - def test_parametrize_all_indirects(self, testdir: Testdir) -> None: - testdir.makepyfile( + def test_parametrize_all_indirects(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -1512,11 +1514,11 @@ def test_2(animal, echo): assert echo in (1, 2, 3) """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["* 3 passed *"]) def test_parametrize_some_arguments_auto_scope( - self, testdir: Testdir, monkeypatch + self, pytester: Pytester, monkeypatch ) -> None: """Integration test for (#3941)""" class_fix_setup: List[object] = [] @@ -1524,7 +1526,7 @@ def test_parametrize_some_arguments_auto_scope( func_fix_setup: List[object] = [] monkeypatch.setattr(sys, "func_fix_setup", func_fix_setup, raising=False) - testdir.makepyfile( + pytester.makepyfile( """ import pytest import sys @@ -1545,13 +1547,13 @@ def test_bar(self): pass """ ) - result = testdir.runpytest_inprocess() + result = pytester.runpytest_inprocess() result.stdout.fnmatch_lines(["* 4 passed in *"]) assert func_fix_setup == [True] * 4 assert class_fix_setup == [10, 20] - def test_parametrize_issue634(self, testdir: Testdir) -> None: - testdir.makepyfile( + def test_parametrize_issue634(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -1579,7 +1581,7 @@ def pytest_generate_tests(metafunc): metafunc.parametrize('foo', params, indirect=True) """ ) - result = testdir.runpytest("-s") + result = pytester.runpytest("-s") output = result.stdout.str() assert output.count("preparing foo-2") == 1 assert output.count("preparing foo-3") == 1 @@ -1588,7 +1590,7 @@ def pytest_generate_tests(metafunc): class TestMarkersWithParametrization: """#308""" - def test_simple_mark(self, testdir: Testdir) -> None: + def test_simple_mark(self, pytester: Pytester) -> None: s = """ import pytest @@ -1601,7 +1603,7 @@ def test_simple_mark(self, testdir: Testdir) -> None: def test_increment(n, expected): assert n + 1 == expected """ - items = testdir.getitems(s) + items = pytester.getitems(s) assert len(items) == 3 for item in items: assert "foo" in item.keywords @@ -1609,7 +1611,7 @@ def test_increment(n, expected): assert "bar" in items[1].keywords assert "bar" not in items[2].keywords - def test_select_based_on_mark(self, testdir: Testdir) -> None: + def test_select_based_on_mark(self, pytester: Pytester) -> None: s = """ import pytest @@ -1621,14 +1623,14 @@ def test_select_based_on_mark(self, testdir: Testdir) -> None: def test_increment(n, expected): assert n + 1 == expected """ - testdir.makepyfile(s) - rec = testdir.inline_run("-m", "foo") + pytester.makepyfile(s) + rec = pytester.inline_run("-m", "foo") passed, skipped, fail = rec.listoutcomes() assert len(passed) == 1 assert len(skipped) == 0 assert len(fail) == 0 - def test_simple_xfail(self, testdir: Testdir) -> None: + def test_simple_xfail(self, pytester: Pytester) -> None: s = """ import pytest @@ -1640,12 +1642,12 @@ def test_simple_xfail(self, testdir: Testdir) -> None: def test_increment(n, expected): assert n + 1 == expected """ - testdir.makepyfile(s) - reprec = testdir.inline_run() + pytester.makepyfile(s) + reprec = pytester.inline_run() # xfail is skip?? reprec.assertoutcome(passed=2, skipped=1) - def test_simple_xfail_single_argname(self, testdir: Testdir) -> None: + def test_simple_xfail_single_argname(self, pytester: Pytester) -> None: s = """ import pytest @@ -1657,11 +1659,11 @@ def test_simple_xfail_single_argname(self, testdir: Testdir) -> None: def test_isEven(n): assert n % 2 == 0 """ - testdir.makepyfile(s) - reprec = testdir.inline_run() + pytester.makepyfile(s) + reprec = pytester.inline_run() reprec.assertoutcome(passed=2, skipped=1) - def test_xfail_with_arg(self, testdir: Testdir) -> None: + def test_xfail_with_arg(self, pytester: Pytester) -> None: s = """ import pytest @@ -1673,11 +1675,11 @@ def test_xfail_with_arg(self, testdir: Testdir) -> None: def test_increment(n, expected): assert n + 1 == expected """ - testdir.makepyfile(s) - reprec = testdir.inline_run() + pytester.makepyfile(s) + reprec = pytester.inline_run() reprec.assertoutcome(passed=2, skipped=1) - def test_xfail_with_kwarg(self, testdir: Testdir) -> None: + def test_xfail_with_kwarg(self, pytester: Pytester) -> None: s = """ import pytest @@ -1689,11 +1691,11 @@ def test_xfail_with_kwarg(self, testdir: Testdir) -> None: def test_increment(n, expected): assert n + 1 == expected """ - testdir.makepyfile(s) - reprec = testdir.inline_run() + pytester.makepyfile(s) + reprec = pytester.inline_run() reprec.assertoutcome(passed=2, skipped=1) - def test_xfail_with_arg_and_kwarg(self, testdir: Testdir) -> None: + def test_xfail_with_arg_and_kwarg(self, pytester: Pytester) -> None: s = """ import pytest @@ -1705,12 +1707,12 @@ def test_xfail_with_arg_and_kwarg(self, testdir: Testdir) -> None: def test_increment(n, expected): assert n + 1 == expected """ - testdir.makepyfile(s) - reprec = testdir.inline_run() + pytester.makepyfile(s) + reprec = pytester.inline_run() reprec.assertoutcome(passed=2, skipped=1) @pytest.mark.parametrize("strict", [True, False]) - def test_xfail_passing_is_xpass(self, testdir: Testdir, strict: bool) -> None: + def test_xfail_passing_is_xpass(self, pytester: Pytester, strict: bool) -> None: s = """ import pytest @@ -1726,12 +1728,12 @@ def test_increment(n, expected): """.format( strict=strict ) - testdir.makepyfile(s) - reprec = testdir.inline_run() + pytester.makepyfile(s) + reprec = pytester.inline_run() passed, failed = (2, 1) if strict else (3, 0) reprec.assertoutcome(passed=passed, failed=failed) - def test_parametrize_called_in_generate_tests(self, testdir: Testdir) -> None: + def test_parametrize_called_in_generate_tests(self, pytester: Pytester) -> None: s = """ import pytest @@ -1750,13 +1752,15 @@ def pytest_generate_tests(metafunc): def test_increment(n, expected): assert n + 1 == expected """ - testdir.makepyfile(s) - reprec = testdir.inline_run() + pytester.makepyfile(s) + reprec = pytester.inline_run() reprec.assertoutcome(passed=2, skipped=2) - def test_parametrize_ID_generation_string_int_works(self, testdir: Testdir) -> None: + def test_parametrize_ID_generation_string_int_works( + self, pytester: Pytester + ) -> None: """#290""" - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -1769,11 +1773,11 @@ def test_limit(limit, myfixture): return """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=2) @pytest.mark.parametrize("strict", [True, False]) - def test_parametrize_marked_value(self, testdir: Testdir, strict: bool) -> None: + def test_parametrize_marked_value(self, pytester: Pytester, strict: bool) -> None: s = """ import pytest @@ -1792,19 +1796,19 @@ def test_increment(n, expected): """.format( strict=strict ) - testdir.makepyfile(s) - reprec = testdir.inline_run() + pytester.makepyfile(s) + reprec = pytester.inline_run() passed, failed = (0, 2) if strict else (2, 0) reprec.assertoutcome(passed=passed, failed=failed) - def test_pytest_make_parametrize_id(self, testdir: Testdir) -> None: - testdir.makeconftest( + def test_pytest_make_parametrize_id(self, pytester: Pytester) -> None: + pytester.makeconftest( """ def pytest_make_parametrize_id(config, val): return str(val * 2) """ ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -1813,17 +1817,17 @@ def test_func(x): pass """ ) - result = testdir.runpytest("-v") + result = pytester.runpytest("-v") result.stdout.fnmatch_lines(["*test_func*0*PASS*", "*test_func*2*PASS*"]) - def test_pytest_make_parametrize_id_with_argname(self, testdir: Testdir) -> None: - testdir.makeconftest( + def test_pytest_make_parametrize_id_with_argname(self, pytester: Pytester) -> None: + pytester.makeconftest( """ def pytest_make_parametrize_id(config, val, argname): return str(val * 2 if argname == 'x' else val * 10) """ ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -1836,13 +1840,13 @@ def test_func_b(y): pass """ ) - result = testdir.runpytest("-v") + result = pytester.runpytest("-v") result.stdout.fnmatch_lines( ["*test_func_a*0*PASS*", "*test_func_a*2*PASS*", "*test_func_b*10*PASS*"] ) - def test_parametrize_positional_args(self, testdir: Testdir) -> None: - testdir.makepyfile( + def test_parametrize_positional_args(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -1851,11 +1855,11 @@ def test_foo(a): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.assert_outcomes(passed=1) - def test_parametrize_iterator(self, testdir: Testdir) -> None: - testdir.makepyfile( + def test_parametrize_iterator(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import itertools import pytest @@ -1877,7 +1881,7 @@ def test_converted_to_str(a, b): pass """ ) - result = testdir.runpytest("-vv", "-s") + result = pytester.runpytest("-vv", "-s") result.stdout.fnmatch_lines( [ "test_parametrize_iterator.py::test1[param0] PASSED", diff --git a/testing/python/raises.py b/testing/python/raises.py index 80634eebfbf..a3991adaef1 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -3,6 +3,7 @@ import pytest from _pytest.outcomes import Failed +from _pytest.pytester import Pytester class TestRaises: @@ -50,8 +51,8 @@ class E(Exception): pprint.pprint(excinfo) raise E() - def test_raises_as_contextmanager(self, testdir): - testdir.makepyfile( + def test_raises_as_contextmanager(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest import _pytest._code @@ -75,11 +76,11 @@ def test_raise_wrong_exception_passes_by(): 1/0 """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*3 passed*"]) - def test_does_not_raise(self, testdir): - testdir.makepyfile( + def test_does_not_raise(self, pytester: Pytester) -> None: + pytester.makepyfile( """ from contextlib import contextmanager import pytest @@ -100,11 +101,11 @@ def test_division(example_input, expectation): assert (6 / example_input) is not None """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*4 passed*"]) - def test_does_not_raise_does_raise(self, testdir): - testdir.makepyfile( + def test_does_not_raise_does_raise(self, pytester: Pytester) -> None: + pytester.makepyfile( """ from contextlib import contextmanager import pytest @@ -123,7 +124,7 @@ def test_division(example_input, expectation): assert (6 / example_input) is not None """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*2 failed*"]) def test_noclass(self) -> None: diff --git a/testing/python/show_fixtures_per_test.py b/testing/python/show_fixtures_per_test.py index ef841819d09..2a15132738d 100644 --- a/testing/python/show_fixtures_per_test.py +++ b/testing/python/show_fixtures_per_test.py @@ -1,11 +1,14 @@ -def test_no_items_should_not_show_output(testdir): - result = testdir.runpytest("--fixtures-per-test") +from _pytest.pytester import Pytester + + +def test_no_items_should_not_show_output(pytester: Pytester) -> None: + result = pytester.runpytest("--fixtures-per-test") result.stdout.no_fnmatch_line("*fixtures used by*") assert result.ret == 0 -def test_fixtures_in_module(testdir): - p = testdir.makepyfile( +def test_fixtures_in_module(pytester: Pytester) -> None: + p = pytester.makepyfile( ''' import pytest @pytest.fixture @@ -19,7 +22,7 @@ def test_arg1(arg1): ''' ) - result = testdir.runpytest("--fixtures-per-test", p) + result = pytester.runpytest("--fixtures-per-test", p) assert result.ret == 0 result.stdout.fnmatch_lines( @@ -33,8 +36,8 @@ def test_arg1(arg1): result.stdout.no_fnmatch_line("*_arg0*") -def test_fixtures_in_conftest(testdir): - testdir.makeconftest( +def test_fixtures_in_conftest(pytester: Pytester) -> None: + pytester.makeconftest( ''' import pytest @pytest.fixture @@ -50,7 +53,7 @@ def arg3(arg1, arg2): """ ''' ) - p = testdir.makepyfile( + p = pytester.makepyfile( """ def test_arg2(arg2): pass @@ -58,7 +61,7 @@ def test_arg3(arg3): pass """ ) - result = testdir.runpytest("--fixtures-per-test", p) + result = pytester.runpytest("--fixtures-per-test", p) assert result.ret == 0 result.stdout.fnmatch_lines( @@ -80,8 +83,8 @@ def test_arg3(arg3): ) -def test_should_show_fixtures_used_by_test(testdir): - testdir.makeconftest( +def test_should_show_fixtures_used_by_test(pytester: Pytester) -> None: + pytester.makeconftest( ''' import pytest @pytest.fixture @@ -92,7 +95,7 @@ def arg2(): """arg2 from conftest""" ''' ) - p = testdir.makepyfile( + p = pytester.makepyfile( ''' import pytest @pytest.fixture @@ -102,7 +105,7 @@ def test_args(arg1, arg2): pass ''' ) - result = testdir.runpytest("--fixtures-per-test", p) + result = pytester.runpytest("--fixtures-per-test", p) assert result.ret == 0 result.stdout.fnmatch_lines( @@ -117,8 +120,8 @@ def test_args(arg1, arg2): ) -def test_verbose_include_private_fixtures_and_loc(testdir): - testdir.makeconftest( +def test_verbose_include_private_fixtures_and_loc(pytester: Pytester) -> None: + pytester.makeconftest( ''' import pytest @pytest.fixture @@ -129,7 +132,7 @@ def arg2(_arg1): """arg2 from conftest""" ''' ) - p = testdir.makepyfile( + p = pytester.makepyfile( ''' import pytest @pytest.fixture @@ -139,7 +142,7 @@ def test_args(arg2, arg3): pass ''' ) - result = testdir.runpytest("--fixtures-per-test", "-v", p) + result = pytester.runpytest("--fixtures-per-test", "-v", p) assert result.ret == 0 result.stdout.fnmatch_lines( @@ -156,8 +159,8 @@ def test_args(arg2, arg3): ) -def test_doctest_items(testdir): - testdir.makepyfile( +def test_doctest_items(pytester: Pytester) -> None: + pytester.makepyfile( ''' def foo(): """ @@ -166,13 +169,13 @@ def foo(): """ ''' ) - testdir.maketxtfile( + pytester.maketxtfile( """ >>> 1 + 1 2 """ ) - result = testdir.runpytest( + result = pytester.runpytest( "--fixtures-per-test", "--doctest-modules", "--doctest-glob=*.txt", "-v" ) assert result.ret == 0 From 92b444a91494a51dfd4d8a127707e5ab4f42c517 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Dec 2020 03:07:27 +0000 Subject: [PATCH 0006/2772] build(deps): bump pytest-bdd in /testing/plugins_integration Bumps [pytest-bdd](https://github.com/pytest-dev/pytest-bdd) from 4.0.1 to 4.0.2. - [Release notes](https://github.com/pytest-dev/pytest-bdd/releases) - [Changelog](https://github.com/pytest-dev/pytest-bdd/blob/master/CHANGES.rst) - [Commits](https://github.com/pytest-dev/pytest-bdd/compare/4.0.1...4.0.2) Signed-off-by: dependabot[bot] --- testing/plugins_integration/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt index d0ee9b571e2..19cc088db83 100644 --- a/testing/plugins_integration/requirements.txt +++ b/testing/plugins_integration/requirements.txt @@ -1,7 +1,7 @@ anyio[curio,trio]==2.0.2 django==3.1.4 pytest-asyncio==0.14.0 -pytest-bdd==4.0.1 +pytest-bdd==4.0.2 pytest-cov==2.10.1 pytest-django==4.1.0 pytest-flakes==4.0.3 From a09d8b15998b78d9e0bbad38879ccb1b00f95069 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Dec 2020 03:07:30 +0000 Subject: [PATCH 0007/2772] build(deps): bump pytest-html in /testing/plugins_integration Bumps [pytest-html](https://github.com/pytest-dev/pytest-html) from 3.1.0 to 3.1.1. - [Release notes](https://github.com/pytest-dev/pytest-html/releases) - [Changelog](https://github.com/pytest-dev/pytest-html/blob/master/CHANGES.rst) - [Commits](https://github.com/pytest-dev/pytest-html/compare/v3.1.0...v3.1.1) Signed-off-by: dependabot[bot] --- testing/plugins_integration/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt index d0ee9b571e2..92a006723ef 100644 --- a/testing/plugins_integration/requirements.txt +++ b/testing/plugins_integration/requirements.txt @@ -5,7 +5,7 @@ pytest-bdd==4.0.1 pytest-cov==2.10.1 pytest-django==4.1.0 pytest-flakes==4.0.3 -pytest-html==3.1.0 +pytest-html==3.1.1 pytest-mock==3.3.1 pytest-rerunfailures==9.1.1 pytest-sugar==0.9.4 From 2cb34a99cbf423c50b7b6592a54f80f68bb9fdc0 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 14 Dec 2020 15:54:59 +0200 Subject: [PATCH 0008/2772] Some py.path.local -> pathlib.Path --- src/_pytest/assertion/rewrite.py | 5 ++- src/_pytest/config/argparsing.py | 15 +++++---- src/_pytest/fixtures.py | 15 ++++----- src/_pytest/main.py | 43 +++++++++++++------------ src/_pytest/monkeypatch.py | 13 ++------ src/_pytest/nodes.py | 10 ++++-- src/_pytest/pathlib.py | 6 ++-- testing/test_assertrewrite.py | 6 ++-- testing/test_main.py | 54 ++++++++++++++------------------ testing/test_nodes.py | 11 +++++-- 10 files changed, 84 insertions(+), 94 deletions(-) diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index 805d4c8b35b..a01be76b4d3 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -27,8 +27,6 @@ from typing import TYPE_CHECKING from typing import Union -import py - from _pytest._io.saferepr import saferepr from _pytest._version import version from _pytest.assertion import util @@ -37,6 +35,7 @@ ) from _pytest.config import Config from _pytest.main import Session +from _pytest.pathlib import absolutepath from _pytest.pathlib import fnmatch_ex from _pytest.store import StoreKey @@ -215,7 +214,7 @@ def _should_rewrite(self, name: str, fn: str, state: "AssertionState") -> bool: return True if self.session is not None: - if self.session.isinitpath(py.path.local(fn)): + if self.session.isinitpath(absolutepath(fn)): state.trace(f"matched test file (was specified on cmdline): {fn!r}") return True diff --git a/src/_pytest/config/argparsing.py b/src/_pytest/config/argparsing.py index 9a481965526..5a09ea781e6 100644 --- a/src/_pytest/config/argparsing.py +++ b/src/_pytest/config/argparsing.py @@ -1,4 +1,5 @@ import argparse +import os import sys import warnings from gettext import gettext @@ -14,8 +15,6 @@ from typing import TYPE_CHECKING from typing import Union -import py - import _pytest._io from _pytest.compat import final from _pytest.config.exceptions import UsageError @@ -97,14 +96,14 @@ def addoption(self, *opts: str, **attrs: Any) -> None: def parse( self, - args: Sequence[Union[str, py.path.local]], + args: Sequence[Union[str, "os.PathLike[str]"]], namespace: Optional[argparse.Namespace] = None, ) -> argparse.Namespace: from _pytest._argcomplete import try_argcomplete self.optparser = self._getparser() try_argcomplete(self.optparser) - strargs = [str(x) if isinstance(x, py.path.local) else x for x in args] + strargs = [os.fspath(x) for x in args] return self.optparser.parse_args(strargs, namespace=namespace) def _getparser(self) -> "MyOptionParser": @@ -128,7 +127,7 @@ def _getparser(self) -> "MyOptionParser": def parse_setoption( self, - args: Sequence[Union[str, py.path.local]], + args: Sequence[Union[str, "os.PathLike[str]"]], option: argparse.Namespace, namespace: Optional[argparse.Namespace] = None, ) -> List[str]: @@ -139,7 +138,7 @@ def parse_setoption( def parse_known_args( self, - args: Sequence[Union[str, py.path.local]], + args: Sequence[Union[str, "os.PathLike[str]"]], namespace: Optional[argparse.Namespace] = None, ) -> argparse.Namespace: """Parse and return a namespace object with known arguments at this point.""" @@ -147,13 +146,13 @@ def parse_known_args( def parse_known_and_unknown_args( self, - args: Sequence[Union[str, py.path.local]], + args: Sequence[Union[str, "os.PathLike[str]"]], namespace: Optional[argparse.Namespace] = None, ) -> Tuple[argparse.Namespace, List[str]]: """Parse and return a namespace object with known arguments, and the remaining arguments unknown at this point.""" optparser = self._getparser() - strargs = [str(x) if isinstance(x, py.path.local) else x for x in args] + strargs = [os.fspath(x) for x in args] return optparser.parse_known_args(strargs, namespace=namespace) def addini( diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 273bcafd393..c24ab7069cb 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -648,12 +648,13 @@ def _compute_fixture_value(self, fixturedef: "FixtureDef[object]") -> None: if has_params: frame = inspect.stack()[3] frameinfo = inspect.getframeinfo(frame[0]) - source_path = py.path.local(frameinfo.filename) + source_path = absolutepath(frameinfo.filename) source_lineno = frameinfo.lineno - rel_source_path = source_path.relto(funcitem.config.rootdir) - if rel_source_path: - source_path_str = rel_source_path - else: + try: + source_path_str = str( + source_path.relative_to(funcitem.config.rootpath) + ) + except ValueError: source_path_str = str(source_path) msg = ( "The requested fixture has no parameter defined for test:\n" @@ -876,7 +877,7 @@ def formatrepr(self) -> "FixtureLookupErrorRepr": class FixtureLookupErrorRepr(TerminalRepr): def __init__( self, - filename: Union[str, py.path.local], + filename: Union[str, "os.PathLike[str]"], firstlineno: int, tblines: Sequence[str], errorstring: str, @@ -903,7 +904,7 @@ def toterminal(self, tw: TerminalWriter) -> None: f"{FormattedExcinfo.flow_marker} {line.strip()}", red=True, ) tw.line() - tw.line("%s:%d" % (self.filename, self.firstlineno + 1)) + tw.line("%s:%d" % (os.fspath(self.filename), self.firstlineno + 1)) def fail_fixturefunc(fixturefunc, msg: str) -> "NoReturn": diff --git a/src/_pytest/main.py b/src/_pytest/main.py index eab3c9afd27..d536f9d8066 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -467,7 +467,7 @@ def __init__(self, config: Config) -> None: self.shouldfail: Union[bool, str] = False self.trace = config.trace.root.get("collection") self.startdir = config.invocation_dir - self._initialpaths: FrozenSet[py.path.local] = frozenset() + self._initialpaths: FrozenSet[Path] = frozenset() self._bestrelpathcache: Dict[Path, str] = _bestrelpath_cache(config.rootpath) @@ -510,8 +510,8 @@ def pytest_runtest_logreport( pytest_collectreport = pytest_runtest_logreport - def isinitpath(self, path: py.path.local) -> bool: - return path in self._initialpaths + def isinitpath(self, path: Union[str, "os.PathLike[str]"]) -> bool: + return Path(path) in self._initialpaths def gethookproxy(self, fspath: "os.PathLike[str]"): # Check if we have the common case of running @@ -601,14 +601,14 @@ def perform_collect( self.trace.root.indent += 1 self._notfound: List[Tuple[str, Sequence[nodes.Collector]]] = [] - self._initial_parts: List[Tuple[py.path.local, List[str]]] = [] + self._initial_parts: List[Tuple[Path, List[str]]] = [] self.items: List[nodes.Item] = [] hook = self.config.hook items: Sequence[Union[nodes.Item, nodes.Collector]] = self.items try: - initialpaths: List[py.path.local] = [] + initialpaths: List[Path] = [] for arg in args: fspath, parts = resolve_collection_argument( self.config.invocation_params.dir, @@ -669,13 +669,13 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: # No point in finding packages when collecting doctests. if not self.config.getoption("doctestmodules", False): pm = self.config.pluginmanager - confcutdir = py.path.local(pm._confcutdir) if pm._confcutdir else None - for parent in reversed(argpath.parts()): - if confcutdir and confcutdir.relto(parent): + confcutdir = pm._confcutdir + for parent in (argpath, *argpath.parents): + if confcutdir and parent in confcutdir.parents: break - if parent.isdir(): - pkginit = parent.join("__init__.py") + if parent.is_dir(): + pkginit = py.path.local(parent / "__init__.py") if pkginit.isfile() and pkginit not in node_cache1: col = self._collectfile(pkginit, handle_dupes=False) if col: @@ -685,7 +685,7 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: # If it's a directory argument, recurse and look for any Subpackages. # Let the Package collector deal with subnodes, don't collect here. - if argpath.check(dir=1): + if argpath.is_dir(): assert not names, "invalid arg {!r}".format((argpath, names)) seen_dirs: Set[py.path.local] = set() @@ -717,15 +717,16 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: node_cache2[key] = x yield x else: - assert argpath.check(file=1) + assert argpath.is_file() - if argpath in node_cache1: - col = node_cache1[argpath] + argpath_ = py.path.local(argpath) + if argpath_ in node_cache1: + col = node_cache1[argpath_] else: - collect_root = pkg_roots.get(argpath.dirname, self) - col = collect_root._collectfile(argpath, handle_dupes=False) + collect_root = pkg_roots.get(argpath_.dirname, self) + col = collect_root._collectfile(argpath_, handle_dupes=False) if col: - node_cache1[argpath] = col + node_cache1[argpath_] = col matching = [] work: List[ @@ -782,9 +783,7 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: # first yielded item will be the __init__ Module itself, so # just use that. If this special case isn't taken, then all the # files in the package will be yielded. - if argpath.basename == "__init__.py" and isinstance( - matching[0], Package - ): + if argpath.name == "__init__.py" and isinstance(matching[0], Package): try: yield next(iter(matching[0].collect())) except StopIteration: @@ -833,7 +832,7 @@ def search_pypath(module_name: str) -> str: def resolve_collection_argument( invocation_path: Path, arg: str, *, as_pypath: bool = False -) -> Tuple[py.path.local, List[str]]: +) -> Tuple[Path, 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 @@ -875,4 +874,4 @@ def resolve_collection_argument( else "directory argument cannot contain :: selection parts: {arg}" ) raise UsageError(msg.format(arg=arg)) - return py.path.local(str(fspath)), parts + return fspath, parts diff --git a/src/_pytest/monkeypatch.py b/src/_pytest/monkeypatch.py index a052f693ac0..d012b8a535a 100644 --- a/src/_pytest/monkeypatch.py +++ b/src/_pytest/monkeypatch.py @@ -4,7 +4,6 @@ import sys import warnings from contextlib import contextmanager -from pathlib import Path from typing import Any from typing import Generator from typing import List @@ -325,20 +324,14 @@ def syspath_prepend(self, path) -> None: invalidate_caches() - def chdir(self, path) -> None: + def chdir(self, path: Union[str, "os.PathLike[str]"]) -> None: """Change the current working directory to the specified path. - Path can be a string or a py.path.local object. + Path can be a string or a path object. """ if self._cwd is None: self._cwd = os.getcwd() - if hasattr(path, "chdir"): - path.chdir() - elif isinstance(path, Path): - # Modern python uses the fspath protocol here LEGACY - os.chdir(str(path)) - else: - os.chdir(path) + os.chdir(path) def undo(self) -> None: """Undo previous changes. diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 98bd581b96d..fee0770eb2b 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -480,10 +480,14 @@ def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None: excinfo.traceback = ntraceback.filter() -def _check_initialpaths_for_relpath(session, fspath): +def _check_initialpaths_for_relpath( + session: "Session", fspath: py.path.local +) -> Optional[str]: for initial_path in session._initialpaths: - if fspath.common(initial_path) == initial_path: - return fspath.relto(initial_path) + initial_path_ = py.path.local(initial_path) + if fspath.common(initial_path_) == initial_path_: + return fspath.relto(initial_path_) + return None class FSCollector(Collector): diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index 8875a28f84b..2e452eb1cc9 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -30,8 +30,6 @@ from typing import TypeVar from typing import Union -import py - from _pytest.compat import assert_never from _pytest.outcomes import skip from _pytest.warning_types import PytestWarning @@ -456,7 +454,7 @@ class ImportPathMismatchError(ImportError): def import_path( - p: Union[str, py.path.local, Path], + p: Union[str, "os.PathLike[str]"], *, mode: Union[str, ImportMode] = ImportMode.prepend, ) -> ModuleType: @@ -482,7 +480,7 @@ def import_path( """ mode = ImportMode(mode) - path = Path(str(p)) + path = Path(p) if not path.exists(): raise ImportError(path) diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index 84d5276e729..ffe18260f90 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -17,8 +17,6 @@ from typing import Optional from typing import Set -import py - import _pytest._code import pytest from _pytest.assertion import util @@ -1311,7 +1309,7 @@ def hook( import importlib.machinery self.find_spec_calls: List[str] = [] - self.initial_paths: Set[py.path.local] = set() + self.initial_paths: Set[Path] = set() class StubSession: _initialpaths = self.initial_paths @@ -1346,7 +1344,7 @@ def fix(): return 1 pytester.makepyfile(test_foo="def test_foo(): pass") pytester.makepyfile(bar="def bar(): pass") foobar_path = pytester.makepyfile(foobar="def foobar(): pass") - self.initial_paths.add(py.path.local(foobar_path)) + self.initial_paths.add(foobar_path) # conftest files should always be rewritten assert hook.find_spec("conftest") is not None diff --git a/testing/test_main.py b/testing/test_main.py index 3e94668e82f..f45607abc30 100644 --- a/testing/test_main.py +++ b/testing/test_main.py @@ -4,13 +4,12 @@ from pathlib import Path from typing import Optional -import py.path - import pytest from _pytest.config import ExitCode from _pytest.config import UsageError from _pytest.main import resolve_collection_argument from _pytest.main import validate_basetemp +from _pytest.pytester import Pytester from _pytest.pytester import Testdir @@ -109,40 +108,37 @@ def test_validate_basetemp_integration(testdir): class TestResolveCollectionArgument: @pytest.fixture - def invocation_dir(self, testdir: Testdir) -> py.path.local: - testdir.syspathinsert(str(testdir.tmpdir / "src")) - testdir.chdir() - - pkg = testdir.tmpdir.join("src/pkg").ensure_dir() - pkg.join("__init__.py").ensure() - pkg.join("test.py").ensure() - return testdir.tmpdir + def invocation_path(self, pytester: Pytester) -> Path: + pytester.syspathinsert(pytester.path / "src") + pytester.chdir() - @pytest.fixture - def invocation_path(self, invocation_dir: py.path.local) -> Path: - return Path(str(invocation_dir)) + pkg = pytester.path.joinpath("src/pkg") + pkg.mkdir(parents=True) + pkg.joinpath("__init__.py").touch() + pkg.joinpath("test.py").touch() + return pytester.path - def test_file(self, invocation_dir: py.path.local, invocation_path: Path) -> None: + def test_file(self, invocation_path: Path) -> None: """File and parts.""" assert resolve_collection_argument(invocation_path, "src/pkg/test.py") == ( - invocation_dir / "src/pkg/test.py", + invocation_path / "src/pkg/test.py", [], ) assert resolve_collection_argument(invocation_path, "src/pkg/test.py::") == ( - invocation_dir / "src/pkg/test.py", + invocation_path / "src/pkg/test.py", [""], ) assert resolve_collection_argument( invocation_path, "src/pkg/test.py::foo::bar" - ) == (invocation_dir / "src/pkg/test.py", ["foo", "bar"]) + ) == (invocation_path / "src/pkg/test.py", ["foo", "bar"]) assert resolve_collection_argument( invocation_path, "src/pkg/test.py::foo::bar::" - ) == (invocation_dir / "src/pkg/test.py", ["foo", "bar", ""]) + ) == (invocation_path / "src/pkg/test.py", ["foo", "bar", ""]) - def test_dir(self, invocation_dir: py.path.local, invocation_path: Path) -> None: + def test_dir(self, invocation_path: Path) -> None: """Directory and parts.""" assert resolve_collection_argument(invocation_path, "src/pkg") == ( - invocation_dir / "src/pkg", + invocation_path / "src/pkg", [], ) @@ -156,16 +152,16 @@ def test_dir(self, invocation_dir: py.path.local, invocation_path: Path) -> None ): resolve_collection_argument(invocation_path, "src/pkg::foo::bar") - def test_pypath(self, invocation_dir: py.path.local, invocation_path: Path) -> None: + def test_pypath(self, invocation_path: Path) -> None: """Dotted name and parts.""" assert resolve_collection_argument( invocation_path, "pkg.test", as_pypath=True - ) == (invocation_dir / "src/pkg/test.py", []) + ) == (invocation_path / "src/pkg/test.py", []) assert resolve_collection_argument( invocation_path, "pkg.test::foo::bar", as_pypath=True - ) == (invocation_dir / "src/pkg/test.py", ["foo", "bar"]) + ) == (invocation_path / "src/pkg/test.py", ["foo", "bar"]) assert resolve_collection_argument(invocation_path, "pkg", as_pypath=True) == ( - invocation_dir / "src/pkg", + invocation_path / "src/pkg", [], ) @@ -191,13 +187,11 @@ def test_does_not_exist(self, invocation_path: Path) -> None: ): resolve_collection_argument(invocation_path, "foobar", as_pypath=True) - def test_absolute_paths_are_resolved_correctly( - self, invocation_dir: py.path.local, invocation_path: Path - ) -> None: + def test_absolute_paths_are_resolved_correctly(self, invocation_path: Path) -> None: """Absolute paths resolve back to absolute paths.""" - full_path = str(invocation_dir / "src") + full_path = str(invocation_path / "src") assert resolve_collection_argument(invocation_path, full_path) == ( - py.path.local(os.path.abspath("src")), + Path(os.path.abspath("src")), [], ) @@ -206,7 +200,7 @@ def test_absolute_paths_are_resolved_correctly( drive, full_path_without_drive = os.path.splitdrive(full_path) assert resolve_collection_argument( invocation_path, full_path_without_drive - ) == (py.path.local(os.path.abspath("src")), []) + ) == (Path(os.path.abspath("src")), []) def test_module_full_path_without_drive(testdir): diff --git a/testing/test_nodes.py b/testing/test_nodes.py index f3824c57090..bae31f0a39c 100644 --- a/testing/test_nodes.py +++ b/testing/test_nodes.py @@ -1,3 +1,4 @@ +from typing import cast from typing import List from typing import Type @@ -73,17 +74,21 @@ def test__check_initialpaths_for_relpath() -> None: class FakeSession1: _initialpaths = [cwd] - assert nodes._check_initialpaths_for_relpath(FakeSession1, cwd) == "" + session = cast(pytest.Session, FakeSession1) + + assert nodes._check_initialpaths_for_relpath(session, cwd) == "" sub = cwd.join("file") class FakeSession2: _initialpaths = [cwd] - assert nodes._check_initialpaths_for_relpath(FakeSession2, sub) == "file" + session = cast(pytest.Session, FakeSession2) + + assert nodes._check_initialpaths_for_relpath(session, sub) == "file" outside = py.path.local("/outside") - assert nodes._check_initialpaths_for_relpath(FakeSession2, outside) is None + assert nodes._check_initialpaths_for_relpath(session, outside) is None def test_failure_with_changed_cwd(pytester: Pytester) -> None: From 592b32bd69cb43aace8cd5525fa0b3712ee767be Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 14 Dec 2020 18:16:14 +0200 Subject: [PATCH 0009/2772] hookspec: add pathlib.Path alternatives to py.path.local parameters in hooks As part of the ongoing migration for py.path to pathlib, make sure all hooks which take a py.path.local also take an equivalent pathlib.Path. --- changelog/8144.feature.rst | 7 +++++++ src/_pytest/hookspec.py | 42 ++++++++++++++++++++++++++++++++------ src/_pytest/main.py | 14 ++++++++----- src/_pytest/python.py | 25 +++++++++++++++-------- src/_pytest/terminal.py | 7 +++++-- testing/test_terminal.py | 6 +++--- 6 files changed, 76 insertions(+), 25 deletions(-) create mode 100644 changelog/8144.feature.rst diff --git a/changelog/8144.feature.rst b/changelog/8144.feature.rst new file mode 100644 index 00000000000..01f40e21521 --- /dev/null +++ b/changelog/8144.feature.rst @@ -0,0 +1,7 @@ +The following hooks now receive an additional ``pathlib.Path`` argument, equivalent to an existing ``py.path.local`` argument: + +- :func:`pytest_ignore_collect <_pytest.hookspec.pytest_ignore_collect>` - The ``fspath`` parameter (equivalent to existing ``path`` parameter). +- :func:`pytest_collect_file <_pytest.hookspec.pytest_collect_file>` - The ``fspath`` parameter (equivalent to existing ``path`` parameter). +- :func:`pytest_pycollect_makemodule <_pytest.hookspec.pytest_pycollect_makemodule>` - The ``fspath`` parameter (equivalent to existing ``path`` parameter). +- :func:`pytest_report_header <_pytest.hookspec.pytest_report_header>` - The ``startpath`` parameter (equivalent to existing ``startdir`` parameter). +- :func:`pytest_report_collectionfinish <_pytest.hookspec.pytest_report_collectionfinish>` - The ``startpath`` parameter (equivalent to existing ``startdir`` parameter). diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index e499b742c7e..22bebf5b783 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -1,5 +1,6 @@ """Hook specifications for pytest plugins which are invoked by pytest itself and by builtin plugins.""" +from pathlib import Path from typing import Any from typing import Dict from typing import List @@ -261,7 +262,9 @@ def pytest_collection_finish(session: "Session") -> None: @hookspec(firstresult=True) -def pytest_ignore_collect(path: py.path.local, config: "Config") -> Optional[bool]: +def pytest_ignore_collect( + fspath: Path, path: py.path.local, config: "Config" +) -> Optional[bool]: """Return True to prevent considering this path for collection. This hook is consulted for all files and directories prior to calling @@ -269,19 +272,29 @@ def pytest_ignore_collect(path: py.path.local, config: "Config") -> Optional[boo Stops at first non-None result, see :ref:`firstresult`. + :param pathlib.Path fspath: The path to analyze. :param py.path.local path: The path to analyze. :param _pytest.config.Config config: The pytest config object. + + .. versionchanged:: 6.3.0 + The ``fspath`` parameter was added as a :class:`pathlib.Path` + equivalent of the ``path`` parameter. """ def pytest_collect_file( - path: py.path.local, parent: "Collector" + fspath: Path, path: py.path.local, parent: "Collector" ) -> "Optional[Collector]": """Create a Collector for the given path, or None if not relevant. The new node needs to have the specified ``parent`` as a parent. + :param pathlib.Path fspath: The path to analyze. :param py.path.local path: The path to collect. + + .. versionchanged:: 6.3.0 + The ``fspath`` parameter was added as a :class:`pathlib.Path` + equivalent of the ``path`` parameter. """ @@ -321,7 +334,9 @@ def pytest_make_collect_report(collector: "Collector") -> "Optional[CollectRepor @hookspec(firstresult=True) -def pytest_pycollect_makemodule(path: py.path.local, parent) -> Optional["Module"]: +def pytest_pycollect_makemodule( + fspath: Path, path: py.path.local, parent +) -> Optional["Module"]: """Return a Module collector or None for the given path. This hook will be called for each matching test module path. @@ -330,7 +345,12 @@ def pytest_pycollect_makemodule(path: py.path.local, parent) -> Optional["Module Stops at first non-None result, see :ref:`firstresult`. - :param py.path.local path: The path of module to collect. + :param pathlib.Path fspath: The path of the module to collect. + :param py.path.local path: The path of the module to collect. + + .. versionchanged:: 6.3.0 + The ``fspath`` parameter was added as a :class:`pathlib.Path` + equivalent of the ``path`` parameter. """ @@ -653,11 +673,12 @@ def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> No def pytest_report_header( - config: "Config", startdir: py.path.local + config: "Config", startpath: Path, startdir: py.path.local ) -> Union[str, List[str]]: """Return a string or list of strings to be displayed as header info for terminal reporting. :param _pytest.config.Config config: The pytest config object. + :param Path startpath: The starting dir. :param py.path.local startdir: The starting dir. .. note:: @@ -672,11 +693,15 @@ def pytest_report_header( This function should be implemented only in plugins or ``conftest.py`` files situated at the tests root directory due to how pytest :ref:`discovers plugins during startup `. + + .. versionchanged:: 6.3.0 + The ``startpath`` parameter was added as a :class:`pathlib.Path` + equivalent of the ``startdir`` parameter. """ def pytest_report_collectionfinish( - config: "Config", startdir: py.path.local, items: Sequence["Item"], + config: "Config", startpath: Path, startdir: py.path.local, items: Sequence["Item"], ) -> Union[str, List[str]]: """Return a string or list of strings to be displayed after collection has finished successfully. @@ -686,6 +711,7 @@ def pytest_report_collectionfinish( .. versionadded:: 3.2 :param _pytest.config.Config config: The pytest config object. + :param Path startpath: The starting path. :param py.path.local startdir: The starting dir. :param items: List of pytest items that are going to be executed; this list should not be modified. @@ -695,6 +721,10 @@ def pytest_report_collectionfinish( ran before it. If you want to have your line(s) displayed first, use :ref:`trylast=True `. + + .. versionchanged:: 6.3.0 + The ``startpath`` parameter was added as a :class:`pathlib.Path` + equivalent of the ``startdir`` parameter. """ diff --git a/src/_pytest/main.py b/src/_pytest/main.py index d536f9d8066..e7c31ecc1d5 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -532,9 +532,10 @@ def gethookproxy(self, fspath: "os.PathLike[str]"): def _recurse(self, direntry: "os.DirEntry[str]") -> bool: if direntry.name == "__pycache__": return False - path = py.path.local(direntry.path) - ihook = self.gethookproxy(path.dirpath()) - if ihook.pytest_ignore_collect(path=path, config=self.config): + fspath = Path(direntry.path) + path = py.path.local(fspath) + ihook = self.gethookproxy(fspath.parent) + if ihook.pytest_ignore_collect(fspath=fspath, path=path, config=self.config): return False norecursepatterns = self.config.getini("norecursedirs") if any(path.check(fnmatch=pat) for pat in norecursepatterns): @@ -544,6 +545,7 @@ def _recurse(self, direntry: "os.DirEntry[str]") -> bool: def _collectfile( self, path: py.path.local, handle_dupes: bool = True ) -> Sequence[nodes.Collector]: + fspath = Path(path) assert ( path.isfile() ), "{!r} is not a file (isdir={!r}, exists={!r}, islink={!r})".format( @@ -551,7 +553,9 @@ def _collectfile( ) ihook = self.gethookproxy(path) if not self.isinitpath(path): - if ihook.pytest_ignore_collect(path=path, config=self.config): + if ihook.pytest_ignore_collect( + fspath=fspath, path=path, config=self.config + ): return () if handle_dupes: @@ -563,7 +567,7 @@ def _collectfile( else: duplicate_paths.add(path) - return ihook.pytest_collect_file(path=path, parent=self) # type: ignore[no-any-return] + return ihook.pytest_collect_file(fspath=fspath, path=path, parent=self) # type: ignore[no-any-return] @overload def perform_collect( diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 407f924a5f1..18e449b9361 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -10,6 +10,7 @@ from collections import Counter from collections import defaultdict from functools import partial +from pathlib import Path from typing import Any from typing import Callable from typing import Dict @@ -187,17 +188,19 @@ def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]: def pytest_collect_file( - path: py.path.local, parent: nodes.Collector + fspath: Path, path: py.path.local, parent: nodes.Collector ) -> Optional["Module"]: ext = path.ext if ext == ".py": - if not parent.session.isinitpath(path): + if not parent.session.isinitpath(fspath): if not path_matches_patterns( path, parent.config.getini("python_files") + ["__init__.py"] ): return None - ihook = parent.session.gethookproxy(path) - module: Module = ihook.pytest_pycollect_makemodule(path=path, parent=parent) + ihook = parent.session.gethookproxy(fspath) + module: Module = ihook.pytest_pycollect_makemodule( + fspath=fspath, path=path, parent=parent + ) return module return None @@ -664,9 +667,10 @@ def isinitpath(self, path: py.path.local) -> bool: def _recurse(self, direntry: "os.DirEntry[str]") -> bool: if direntry.name == "__pycache__": return False - path = py.path.local(direntry.path) - ihook = self.session.gethookproxy(path.dirpath()) - if ihook.pytest_ignore_collect(path=path, config=self.config): + fspath = Path(direntry.path) + path = py.path.local(fspath) + ihook = self.session.gethookproxy(fspath.parent) + if ihook.pytest_ignore_collect(fspath=fspath, path=path, config=self.config): return False norecursepatterns = self.config.getini("norecursedirs") if any(path.check(fnmatch=pat) for pat in norecursepatterns): @@ -676,6 +680,7 @@ def _recurse(self, direntry: "os.DirEntry[str]") -> bool: def _collectfile( self, path: py.path.local, handle_dupes: bool = True ) -> Sequence[nodes.Collector]: + fspath = Path(path) assert ( path.isfile() ), "{!r} is not a file (isdir={!r}, exists={!r}, islink={!r})".format( @@ -683,7 +688,9 @@ def _collectfile( ) ihook = self.session.gethookproxy(path) if not self.session.isinitpath(path): - if ihook.pytest_ignore_collect(path=path, config=self.config): + if ihook.pytest_ignore_collect( + fspath=fspath, path=path, config=self.config + ): return () if handle_dupes: @@ -695,7 +702,7 @@ def _collectfile( else: duplicate_paths.add(path) - return ihook.pytest_collect_file(path=path, parent=self) # type: ignore[no-any-return] + return ihook.pytest_collect_file(fspath=fspath, path=path, parent=self) # type: ignore[no-any-return] def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: this_path = self.fspath.dirpath() diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index 0e0ed70e5be..39adfaaa310 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -710,7 +710,7 @@ def pytest_sessionstart(self, session: "Session") -> None: msg += " -- " + str(sys.executable) self.write_line(msg) lines = self.config.hook.pytest_report_header( - config=self.config, startdir=self.startdir + config=self.config, startpath=self.startpath, startdir=self.startdir ) self._write_report_lines_from_hooks(lines) @@ -745,7 +745,10 @@ def pytest_collection_finish(self, session: "Session") -> None: self.report_collect(True) lines = self.config.hook.pytest_report_collectionfinish( - config=self.config, startdir=self.startdir, items=session.items + config=self.config, + startpath=self.startpath, + startdir=self.startdir, + items=session.items, ) self._write_report_lines_from_hooks(lines) diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 7ad5849d4b9..6319188a75e 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -1010,7 +1010,7 @@ def test_more_quiet_reporting(self, pytester: Pytester) -> None: def test_report_collectionfinish_hook(self, pytester: Pytester, params) -> None: pytester.makeconftest( """ - def pytest_report_collectionfinish(config, startdir, items): + def pytest_report_collectionfinish(config, startpath, startdir, items): return ['hello from hook: {0} items'.format(len(items))] """ ) @@ -1436,8 +1436,8 @@ def pytest_report_header(config): ) pytester.mkdir("a").joinpath("conftest.py").write_text( """ -def pytest_report_header(config, startdir): - return ["line1", str(startdir)] +def pytest_report_header(config, startdir, startpath): + return ["line1", str(startpath)] """ ) result = pytester.runpytest("a") From 8eef8c6004af955f1074905e9656480eeeb3bb67 Mon Sep 17 00:00:00 2001 From: Anton <44246099+antonblr@users.noreply.github.com> Date: Tue, 15 Dec 2020 03:02:32 -0800 Subject: [PATCH 0010/2772] tests: Migrate to pytester - incremental update (#8145) --- testing/code/test_excinfo.py | 57 ++-- testing/examples/test_issue519.py | 9 +- testing/io/test_terminalwriter.py | 5 +- testing/logging/test_fixture.py | 50 +-- testing/logging/test_reporting.py | 275 +++++++-------- testing/test_cacheprovider.py | 533 ++++++++++++++++-------------- testing/test_conftest.py | 21 +- testing/test_monkeypatch.py | 68 ++-- testing/test_pluginmanager.py | 131 +++++--- testing/test_reports.py | 102 +++--- testing/test_runner.py | 253 +++++++------- 11 files changed, 801 insertions(+), 703 deletions(-) diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index 5b9e3eda529..44d7ab549e8 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -5,6 +5,7 @@ import queue import sys import textwrap +from pathlib import Path from typing import Any from typing import Dict from typing import Tuple @@ -19,7 +20,10 @@ from _pytest._code.code import ExceptionInfo from _pytest._code.code import FormattedExcinfo from _pytest._io import TerminalWriter +from _pytest.pathlib import import_path from _pytest.pytester import LineMatcher +from _pytest.pytester import Pytester + if TYPE_CHECKING: from _pytest._code.code import _TracebackStyle @@ -155,10 +159,10 @@ def test_traceback_cut(self): newtraceback = traceback.cut(path=path, lineno=firstlineno + 2) assert len(newtraceback) == 1 - def test_traceback_cut_excludepath(self, testdir): - p = testdir.makepyfile("def f(): raise ValueError") + def test_traceback_cut_excludepath(self, pytester: Pytester) -> None: + p = pytester.makepyfile("def f(): raise ValueError") with pytest.raises(ValueError) as excinfo: - p.pyimport().f() + import_path(p).f() # type: ignore[attr-defined] basedir = py.path.local(pytest.__file__).dirpath() newtraceback = excinfo.traceback.cut(excludepath=basedir) for x in newtraceback: @@ -406,8 +410,8 @@ def test_match_succeeds(): excinfo.match(r".*zero.*") -def test_match_raises_error(testdir): - testdir.makepyfile( +def test_match_raises_error(pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest def test_division_zero(): @@ -416,14 +420,14 @@ def test_division_zero(): excinfo.match(r'[123]+') """ ) - result = testdir.runpytest() + result = pytester.runpytest() assert result.ret != 0 exc_msg = "Regex pattern '[[]123[]]+' does not match 'division by zero'." result.stdout.fnmatch_lines([f"E * AssertionError: {exc_msg}"]) result.stdout.no_fnmatch_line("*__tracebackhide__ = True*") - result = testdir.runpytest("--fulltrace") + result = pytester.runpytest("--fulltrace") assert result.ret != 0 result.stdout.fnmatch_lines( ["*__tracebackhide__ = True*", f"E * AssertionError: {exc_msg}"] @@ -432,15 +436,14 @@ def test_division_zero(): class TestFormattedExcinfo: @pytest.fixture - def importasmod(self, request, _sys_snapshot): + def importasmod(self, tmp_path: Path, _sys_snapshot): def importasmod(source): source = textwrap.dedent(source) - tmpdir = request.getfixturevalue("tmpdir") - modpath = tmpdir.join("mod.py") - tmpdir.ensure("__init__.py") - modpath.write(source) + modpath = tmp_path.joinpath("mod.py") + tmp_path.joinpath("__init__.py").touch() + modpath.write_text(source) importlib.invalidate_caches() - return modpath.pyimport() + return import_path(modpath) return importasmod @@ -682,7 +685,7 @@ def entry(): p = FormattedExcinfo(style="short") reprtb = p.repr_traceback_entry(excinfo.traceback[-2]) lines = reprtb.lines - basename = py.path.local(mod.__file__).basename + basename = Path(mod.__file__).name assert lines[0] == " func1()" assert reprtb.reprfileloc is not None assert basename in str(reprtb.reprfileloc.path) @@ -948,7 +951,9 @@ def f(): assert line.endswith("mod.py") assert tw_mock.lines[12] == ":3: ValueError" - def test_toterminal_long_missing_source(self, importasmod, tmpdir, tw_mock): + def test_toterminal_long_missing_source( + self, importasmod, tmp_path: Path, tw_mock + ) -> None: mod = importasmod( """ def g(x): @@ -958,7 +963,7 @@ def f(): """ ) excinfo = pytest.raises(ValueError, mod.f) - tmpdir.join("mod.py").remove() + tmp_path.joinpath("mod.py").unlink() excinfo.traceback = excinfo.traceback.filter() repr = excinfo.getrepr() repr.toterminal(tw_mock) @@ -978,7 +983,9 @@ def f(): assert line.endswith("mod.py") assert tw_mock.lines[10] == ":3: ValueError" - def test_toterminal_long_incomplete_source(self, importasmod, tmpdir, tw_mock): + def test_toterminal_long_incomplete_source( + self, importasmod, tmp_path: Path, tw_mock + ) -> None: mod = importasmod( """ def g(x): @@ -988,7 +995,7 @@ def f(): """ ) excinfo = pytest.raises(ValueError, mod.f) - tmpdir.join("mod.py").write("asdf") + tmp_path.joinpath("mod.py").write_text("asdf") excinfo.traceback = excinfo.traceback.filter() repr = excinfo.getrepr() repr.toterminal(tw_mock) @@ -1374,16 +1381,18 @@ def test_repr_traceback_with_unicode(style, encoding): assert repr_traceback is not None -def test_cwd_deleted(testdir): - testdir.makepyfile( +def test_cwd_deleted(pytester: Pytester) -> None: + pytester.makepyfile( """ - def test(tmpdir): - tmpdir.chdir() - tmpdir.remove() + import os + + def test(tmp_path): + os.chdir(tmp_path) + tmp_path.unlink() assert False """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["* 1 failed in *"]) result.stdout.no_fnmatch_line("*INTERNALERROR*") result.stderr.no_fnmatch_line("*INTERNALERROR*") diff --git a/testing/examples/test_issue519.py b/testing/examples/test_issue519.py index e83f18fdc93..85ba545e671 100644 --- a/testing/examples/test_issue519.py +++ b/testing/examples/test_issue519.py @@ -1,3 +1,6 @@ -def test_510(testdir): - testdir.copy_example("issue_519.py") - testdir.runpytest("issue_519.py") +from _pytest.pytester import Pytester + + +def test_510(pytester: Pytester) -> None: + pytester.copy_example("issue_519.py") + pytester.runpytest("issue_519.py") diff --git a/testing/io/test_terminalwriter.py b/testing/io/test_terminalwriter.py index db0ccf06a40..fac7593eadd 100644 --- a/testing/io/test_terminalwriter.py +++ b/testing/io/test_terminalwriter.py @@ -3,6 +3,7 @@ import re import shutil import sys +from pathlib import Path from typing import Generator from unittest import mock @@ -64,10 +65,10 @@ def test_terminalwriter_not_unicode() -> None: class TestTerminalWriter: @pytest.fixture(params=["path", "stringio"]) def tw( - self, request, tmpdir + self, request, tmp_path: Path ) -> Generator[terminalwriter.TerminalWriter, None, None]: if request.param == "path": - p = tmpdir.join("tmpfile") + p = tmp_path.joinpath("tmpfile") f = open(str(p), "w+", encoding="utf8") tw = terminalwriter.TerminalWriter(f) diff --git a/testing/logging/test_fixture.py b/testing/logging/test_fixture.py index ffd51bcad7a..f82df19715b 100644 --- a/testing/logging/test_fixture.py +++ b/testing/logging/test_fixture.py @@ -2,14 +2,14 @@ import pytest from _pytest.logging import caplog_records_key -from _pytest.pytester import Testdir +from _pytest.pytester import Pytester logger = logging.getLogger(__name__) sublogger = logging.getLogger(__name__ + ".baz") -def test_fixture_help(testdir): - result = testdir.runpytest("--fixtures") +def test_fixture_help(pytester: Pytester) -> None: + result = pytester.runpytest("--fixtures") result.stdout.fnmatch_lines(["*caplog*"]) @@ -28,12 +28,12 @@ def test_change_level(caplog): assert "CRITICAL" in caplog.text -def test_change_level_undo(testdir: Testdir) -> None: +def test_change_level_undo(pytester: Pytester) -> None: """Ensure that 'set_level' is undone after the end of the test. Tests the logging output themselves (affacted both by logger and handler levels). """ - testdir.makepyfile( + pytester.makepyfile( """ import logging @@ -49,17 +49,17 @@ def test2(caplog): assert 0 """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*log from test1*", "*2 failed in *"]) result.stdout.no_fnmatch_line("*log from test2*") -def test_change_level_undos_handler_level(testdir: Testdir) -> None: +def test_change_level_undos_handler_level(pytester: Pytester) -> None: """Ensure that 'set_level' is undone after the end of the test (handler). Issue #7569. Tests the handler level specifically. """ - testdir.makepyfile( + pytester.makepyfile( """ import logging @@ -78,7 +78,7 @@ def test3(caplog): assert caplog.handler.level == 43 """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.assert_outcomes(passed=3) @@ -172,8 +172,8 @@ def test_caplog_captures_for_all_stages(caplog, logging_during_setup_and_teardow assert set(caplog._item._store[caplog_records_key]) == {"setup", "call"} -def test_ini_controls_global_log_level(testdir): - testdir.makepyfile( +def test_ini_controls_global_log_level(pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest import logging @@ -187,20 +187,20 @@ def test_log_level_override(request, caplog): assert 'ERROR' in caplog.text """ ) - testdir.makeini( + pytester.makeini( """ [pytest] log_level=ERROR """ ) - result = testdir.runpytest() + result = pytester.runpytest() # make sure that that we get a '0' exit code for the testsuite assert result.ret == 0 -def test_caplog_can_override_global_log_level(testdir): - testdir.makepyfile( +def test_caplog_can_override_global_log_level(pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest import logging @@ -227,19 +227,19 @@ def test_log_level_override(request, caplog): assert "message won't be shown" not in caplog.text """ ) - testdir.makeini( + pytester.makeini( """ [pytest] log_level=WARNING """ ) - result = testdir.runpytest() + result = pytester.runpytest() assert result.ret == 0 -def test_caplog_captures_despite_exception(testdir): - testdir.makepyfile( +def test_caplog_captures_despite_exception(pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest import logging @@ -255,26 +255,28 @@ def test_log_level_override(request, caplog): raise Exception() """ ) - testdir.makeini( + pytester.makeini( """ [pytest] log_level=WARNING """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*ERROR message will be shown*"]) result.stdout.no_fnmatch_line("*DEBUG message won't be shown*") assert result.ret == 1 -def test_log_report_captures_according_to_config_option_upon_failure(testdir): +def test_log_report_captures_according_to_config_option_upon_failure( + pytester: Pytester, +) -> None: """Test that upon failure: (1) `caplog` succeeded to capture the DEBUG message and assert on it => No `Exception` is raised. (2) The `DEBUG` message does NOT appear in the `Captured log call` report. (3) The stdout, `INFO`, and `WARNING` messages DO appear in the test reports due to `--log-level=INFO`. """ - testdir.makepyfile( + pytester.makepyfile( """ import pytest import logging @@ -299,7 +301,7 @@ def test_that_fails(request, caplog): """ ) - result = testdir.runpytest("--log-level=INFO") + result = pytester.runpytest("--log-level=INFO") result.stdout.no_fnmatch_line("*Exception: caplog failed to capture DEBUG*") result.stdout.no_fnmatch_line("*DEBUG log message*") result.stdout.fnmatch_lines( diff --git a/testing/logging/test_reporting.py b/testing/logging/test_reporting.py index fc9f1082346..a5ab8b98ba7 100644 --- a/testing/logging/test_reporting.py +++ b/testing/logging/test_reporting.py @@ -6,12 +6,13 @@ import pytest from _pytest.capture import CaptureManager from _pytest.config import ExitCode -from _pytest.pytester import Testdir +from _pytest.fixtures import FixtureRequest +from _pytest.pytester import Pytester from _pytest.terminal import TerminalReporter -def test_nothing_logged(testdir): - testdir.makepyfile( +def test_nothing_logged(pytester: Pytester) -> None: + pytester.makepyfile( """ import sys @@ -21,7 +22,7 @@ def test_foo(): assert False """ ) - result = testdir.runpytest() + result = pytester.runpytest() assert result.ret == 1 result.stdout.fnmatch_lines(["*- Captured stdout call -*", "text going to stdout"]) result.stdout.fnmatch_lines(["*- Captured stderr call -*", "text going to stderr"]) @@ -29,8 +30,8 @@ def test_foo(): result.stdout.fnmatch_lines(["*- Captured *log call -*"]) -def test_messages_logged(testdir): - testdir.makepyfile( +def test_messages_logged(pytester: Pytester) -> None: + pytester.makepyfile( """ import sys import logging @@ -44,15 +45,15 @@ def test_foo(): assert False """ ) - result = testdir.runpytest("--log-level=INFO") + result = pytester.runpytest("--log-level=INFO") assert result.ret == 1 result.stdout.fnmatch_lines(["*- Captured *log call -*", "*text going to logger*"]) result.stdout.fnmatch_lines(["*- Captured stdout call -*", "text going to stdout"]) result.stdout.fnmatch_lines(["*- Captured stderr call -*", "text going to stderr"]) -def test_root_logger_affected(testdir): - testdir.makepyfile( +def test_root_logger_affected(pytester: Pytester) -> None: + pytester.makepyfile( """ import logging logger = logging.getLogger() @@ -65,8 +66,8 @@ def test_foo(): assert 0 """ ) - log_file = testdir.tmpdir.join("pytest.log").strpath - result = testdir.runpytest("--log-level=ERROR", "--log-file=pytest.log") + log_file = str(pytester.path.joinpath("pytest.log")) + result = pytester.runpytest("--log-level=ERROR", "--log-file=pytest.log") assert result.ret == 1 # The capture log calls in the stdout section only contain the @@ -87,8 +88,8 @@ def test_foo(): assert "error text going to logger" in contents -def test_log_cli_level_log_level_interaction(testdir): - testdir.makepyfile( +def test_log_cli_level_log_level_interaction(pytester: Pytester) -> None: + pytester.makepyfile( """ import logging logger = logging.getLogger() @@ -102,7 +103,7 @@ def test_foo(): """ ) - result = testdir.runpytest("--log-cli-level=INFO", "--log-level=ERROR") + result = pytester.runpytest("--log-cli-level=INFO", "--log-level=ERROR") assert result.ret == 1 result.stdout.fnmatch_lines( @@ -117,8 +118,8 @@ def test_foo(): result.stdout.no_re_match_line("DEBUG") -def test_setup_logging(testdir): - testdir.makepyfile( +def test_setup_logging(pytester: Pytester) -> None: + pytester.makepyfile( """ import logging @@ -132,7 +133,7 @@ def test_foo(): assert False """ ) - result = testdir.runpytest("--log-level=INFO") + result = pytester.runpytest("--log-level=INFO") assert result.ret == 1 result.stdout.fnmatch_lines( [ @@ -144,8 +145,8 @@ def test_foo(): ) -def test_teardown_logging(testdir): - testdir.makepyfile( +def test_teardown_logging(pytester: Pytester) -> None: + pytester.makepyfile( """ import logging @@ -159,7 +160,7 @@ def teardown_function(function): assert False """ ) - result = testdir.runpytest("--log-level=INFO") + result = pytester.runpytest("--log-level=INFO") assert result.ret == 1 result.stdout.fnmatch_lines( [ @@ -172,9 +173,9 @@ def teardown_function(function): @pytest.mark.parametrize("enabled", [True, False]) -def test_log_cli_enabled_disabled(testdir, enabled): +def test_log_cli_enabled_disabled(pytester: Pytester, enabled: bool) -> None: msg = "critical message logged by test" - testdir.makepyfile( + pytester.makepyfile( """ import logging def test_log_cli(): @@ -184,13 +185,13 @@ def test_log_cli(): ) ) if enabled: - testdir.makeini( + pytester.makeini( """ [pytest] log_cli=true """ ) - result = testdir.runpytest() + result = pytester.runpytest() if enabled: result.stdout.fnmatch_lines( [ @@ -204,9 +205,9 @@ def test_log_cli(): assert msg not in result.stdout.str() -def test_log_cli_default_level(testdir): +def test_log_cli_default_level(pytester: Pytester) -> None: # Default log file level - testdir.makepyfile( + pytester.makepyfile( """ import pytest import logging @@ -217,14 +218,14 @@ def test_log_cli(request): logging.getLogger('catchlog').warning("WARNING message will be shown") """ ) - testdir.makeini( + pytester.makeini( """ [pytest] log_cli=true """ ) - result = testdir.runpytest() + result = pytester.runpytest() # fnmatch_lines does an assertion internally result.stdout.fnmatch_lines( @@ -238,10 +239,12 @@ def test_log_cli(request): assert result.ret == 0 -def test_log_cli_default_level_multiple_tests(testdir, request): +def test_log_cli_default_level_multiple_tests( + pytester: Pytester, request: FixtureRequest +) -> None: """Ensure we reset the first newline added by the live logger between tests""" filename = request.node.name + ".py" - testdir.makepyfile( + pytester.makepyfile( """ import logging @@ -252,14 +255,14 @@ def test_log_2(): logging.warning("log message from test_log_2") """ ) - testdir.makeini( + pytester.makeini( """ [pytest] log_cli=true """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( [ f"{filename}::test_log_1 ", @@ -273,11 +276,13 @@ def test_log_2(): ) -def test_log_cli_default_level_sections(testdir, request): +def test_log_cli_default_level_sections( + pytester: Pytester, request: FixtureRequest +) -> None: """Check that with live logging enable we are printing the correct headers during start/setup/call/teardown/finish.""" filename = request.node.name + ".py" - testdir.makeconftest( + pytester.makeconftest( """ import pytest import logging @@ -290,7 +295,7 @@ def pytest_runtest_logfinish(): """ ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest import logging @@ -308,14 +313,14 @@ def test_log_2(fix): logging.warning("log message from test_log_2") """ ) - testdir.makeini( + pytester.makeini( """ [pytest] log_cli=true """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( [ f"{filename}::test_log_1 ", @@ -347,11 +352,13 @@ def test_log_2(fix): ) -def test_live_logs_unknown_sections(testdir, request): +def test_live_logs_unknown_sections( + pytester: Pytester, request: FixtureRequest +) -> None: """Check that with live logging enable we are printing the correct headers during start/setup/call/teardown/finish.""" filename = request.node.name + ".py" - testdir.makeconftest( + pytester.makeconftest( """ import pytest import logging @@ -367,7 +374,7 @@ def pytest_runtest_logfinish(): """ ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest import logging @@ -383,14 +390,14 @@ def test_log_1(fix): """ ) - testdir.makeini( + pytester.makeini( """ [pytest] log_cli=true """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( [ "*WARNING*Unknown Section*", @@ -409,11 +416,13 @@ def test_log_1(fix): ) -def test_sections_single_new_line_after_test_outcome(testdir, request): +def test_sections_single_new_line_after_test_outcome( + pytester: Pytester, request: FixtureRequest +) -> None: """Check that only a single new line is written between log messages during teardown/finish.""" filename = request.node.name + ".py" - testdir.makeconftest( + pytester.makeconftest( """ import pytest import logging @@ -427,7 +436,7 @@ def pytest_runtest_logfinish(): """ ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest import logging @@ -443,14 +452,14 @@ def test_log_1(fix): logging.warning("log message from test_log_1") """ ) - testdir.makeini( + pytester.makeini( """ [pytest] log_cli=true """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( [ f"{filename}::test_log_1 ", @@ -487,9 +496,9 @@ def test_log_1(fix): ) -def test_log_cli_level(testdir): +def test_log_cli_level(pytester: Pytester) -> None: # Default log file level - testdir.makepyfile( + pytester.makepyfile( """ import pytest import logging @@ -501,14 +510,14 @@ def test_log_cli(request): print('PASSED') """ ) - testdir.makeini( + pytester.makeini( """ [pytest] log_cli=true """ ) - result = testdir.runpytest("-s", "--log-cli-level=INFO") + result = pytester.runpytest("-s", "--log-cli-level=INFO") # fnmatch_lines does an assertion internally result.stdout.fnmatch_lines( @@ -522,7 +531,7 @@ def test_log_cli(request): # make sure that that we get a '0' exit code for the testsuite assert result.ret == 0 - result = testdir.runpytest("-s", "--log-level=INFO") + result = pytester.runpytest("-s", "--log-level=INFO") # fnmatch_lines does an assertion internally result.stdout.fnmatch_lines( @@ -537,15 +546,15 @@ def test_log_cli(request): assert result.ret == 0 -def test_log_cli_ini_level(testdir): - testdir.makeini( +def test_log_cli_ini_level(pytester: Pytester) -> None: + pytester.makeini( """ [pytest] log_cli=true log_cli_level = INFO """ ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest import logging @@ -558,7 +567,7 @@ def test_log_cli(request): """ ) - result = testdir.runpytest("-s") + result = pytester.runpytest("-s") # fnmatch_lines does an assertion internally result.stdout.fnmatch_lines( @@ -577,11 +586,11 @@ def test_log_cli(request): "cli_args", ["", "--log-level=WARNING", "--log-file-level=WARNING", "--log-cli-level=WARNING"], ) -def test_log_cli_auto_enable(testdir, cli_args): +def test_log_cli_auto_enable(pytester: Pytester, cli_args: str) -> None: """Check that live logs are enabled if --log-level or --log-cli-level is passed on the CLI. It should not be auto enabled if the same configs are set on the INI file. """ - testdir.makepyfile( + pytester.makepyfile( """ import logging @@ -591,7 +600,7 @@ def test_log_1(): """ ) - testdir.makeini( + pytester.makeini( """ [pytest] log_level=INFO @@ -599,7 +608,7 @@ def test_log_1(): """ ) - result = testdir.runpytest(cli_args) + result = pytester.runpytest(cli_args) stdout = result.stdout.str() if cli_args == "--log-cli-level=WARNING": result.stdout.fnmatch_lines( @@ -620,9 +629,9 @@ def test_log_1(): assert "WARNING" not in stdout -def test_log_file_cli(testdir): +def test_log_file_cli(pytester: Pytester) -> None: # Default log file level - testdir.makepyfile( + pytester.makepyfile( """ import pytest import logging @@ -635,9 +644,9 @@ def test_log_file(request): """ ) - log_file = testdir.tmpdir.join("pytest.log").strpath + log_file = str(pytester.path.joinpath("pytest.log")) - result = testdir.runpytest( + result = pytester.runpytest( "-s", f"--log-file={log_file}", "--log-file-level=WARNING" ) @@ -653,9 +662,9 @@ def test_log_file(request): assert "This log message won't be shown" not in contents -def test_log_file_cli_level(testdir): +def test_log_file_cli_level(pytester: Pytester) -> None: # Default log file level - testdir.makepyfile( + pytester.makepyfile( """ import pytest import logging @@ -668,9 +677,9 @@ def test_log_file(request): """ ) - log_file = testdir.tmpdir.join("pytest.log").strpath + log_file = str(pytester.path.joinpath("pytest.log")) - result = testdir.runpytest("-s", f"--log-file={log_file}", "--log-file-level=INFO") + result = pytester.runpytest("-s", f"--log-file={log_file}", "--log-file-level=INFO") # fnmatch_lines does an assertion internally result.stdout.fnmatch_lines(["test_log_file_cli_level.py PASSED"]) @@ -684,22 +693,22 @@ def test_log_file(request): assert "This log message won't be shown" not in contents -def test_log_level_not_changed_by_default(testdir): - testdir.makepyfile( +def test_log_level_not_changed_by_default(pytester: Pytester) -> None: + pytester.makepyfile( """ import logging def test_log_file(): assert logging.getLogger().level == logging.WARNING """ ) - result = testdir.runpytest("-s") + result = pytester.runpytest("-s") result.stdout.fnmatch_lines(["* 1 passed in *"]) -def test_log_file_ini(testdir): - log_file = testdir.tmpdir.join("pytest.log").strpath +def test_log_file_ini(pytester: Pytester) -> None: + log_file = str(pytester.path.joinpath("pytest.log")) - testdir.makeini( + pytester.makeini( """ [pytest] log_file={} @@ -708,7 +717,7 @@ def test_log_file_ini(testdir): log_file ) ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest import logging @@ -721,7 +730,7 @@ def test_log_file(request): """ ) - result = testdir.runpytest("-s") + result = pytester.runpytest("-s") # fnmatch_lines does an assertion internally result.stdout.fnmatch_lines(["test_log_file_ini.py PASSED"]) @@ -735,10 +744,10 @@ def test_log_file(request): assert "This log message won't be shown" not in contents -def test_log_file_ini_level(testdir): - log_file = testdir.tmpdir.join("pytest.log").strpath +def test_log_file_ini_level(pytester: Pytester) -> None: + log_file = str(pytester.path.joinpath("pytest.log")) - testdir.makeini( + pytester.makeini( """ [pytest] log_file={} @@ -747,7 +756,7 @@ def test_log_file_ini_level(testdir): log_file ) ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest import logging @@ -760,7 +769,7 @@ def test_log_file(request): """ ) - result = testdir.runpytest("-s") + result = pytester.runpytest("-s") # fnmatch_lines does an assertion internally result.stdout.fnmatch_lines(["test_log_file_ini_level.py PASSED"]) @@ -774,10 +783,10 @@ def test_log_file(request): assert "This log message won't be shown" not in contents -def test_log_file_unicode(testdir): - log_file = testdir.tmpdir.join("pytest.log").strpath +def test_log_file_unicode(pytester: Pytester) -> None: + log_file = str(pytester.path.joinpath("pytest.log")) - testdir.makeini( + pytester.makeini( """ [pytest] log_file={} @@ -786,7 +795,7 @@ def test_log_file_unicode(testdir): log_file ) ) - testdir.makepyfile( + pytester.makepyfile( """\ import logging @@ -797,7 +806,7 @@ def test_log_file(): """ ) - result = testdir.runpytest() + result = pytester.runpytest() # make sure that that we get a '0' exit code for the testsuite assert result.ret == 0 @@ -810,11 +819,13 @@ def test_log_file(): @pytest.mark.parametrize("has_capture_manager", [True, False]) -def test_live_logging_suspends_capture(has_capture_manager: bool, request) -> None: +def test_live_logging_suspends_capture( + has_capture_manager: bool, request: FixtureRequest +) -> None: """Test that capture manager is suspended when we emitting messages for live logging. This tests the implementation calls instead of behavior because it is difficult/impossible to do it using - ``testdir`` facilities because they do their own capturing. + ``pytester`` facilities because they do their own capturing. We parametrize the test to also make sure _LiveLoggingStreamHandler works correctly if no capture manager plugin is installed. @@ -856,8 +867,8 @@ def section(self, *args, **kwargs): assert cast(io.StringIO, out_file).getvalue() == "\nsome message\n" -def test_collection_live_logging(testdir): - testdir.makepyfile( +def test_collection_live_logging(pytester: Pytester) -> None: + pytester.makepyfile( """ import logging @@ -865,22 +876,22 @@ def test_collection_live_logging(testdir): """ ) - result = testdir.runpytest("--log-cli-level=INFO") + result = pytester.runpytest("--log-cli-level=INFO") result.stdout.fnmatch_lines( ["*--- live log collection ---*", "*Normal message*", "collected 0 items"] ) @pytest.mark.parametrize("verbose", ["", "-q", "-qq"]) -def test_collection_collect_only_live_logging(testdir, verbose): - testdir.makepyfile( +def test_collection_collect_only_live_logging(pytester: Pytester, verbose: str) -> None: + pytester.makepyfile( """ def test_simple(): pass """ ) - result = testdir.runpytest("--collect-only", "--log-cli-level=INFO", verbose) + result = pytester.runpytest("--collect-only", "--log-cli-level=INFO", verbose) expected_lines = [] @@ -907,10 +918,10 @@ def test_simple(): result.stdout.fnmatch_lines(expected_lines) -def test_collection_logging_to_file(testdir): - log_file = testdir.tmpdir.join("pytest.log").strpath +def test_collection_logging_to_file(pytester: Pytester) -> None: + log_file = str(pytester.path.joinpath("pytest.log")) - testdir.makeini( + pytester.makeini( """ [pytest] log_file={} @@ -920,7 +931,7 @@ def test_collection_logging_to_file(testdir): ) ) - testdir.makepyfile( + pytester.makepyfile( """ import logging @@ -932,7 +943,7 @@ def test_simple(): """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.no_fnmatch_line("*--- live log collection ---*") @@ -945,10 +956,10 @@ def test_simple(): assert "info message in test_simple" in contents -def test_log_in_hooks(testdir): - log_file = testdir.tmpdir.join("pytest.log").strpath +def test_log_in_hooks(pytester: Pytester) -> None: + log_file = str(pytester.path.joinpath("pytest.log")) - testdir.makeini( + pytester.makeini( """ [pytest] log_file={} @@ -958,7 +969,7 @@ def test_log_in_hooks(testdir): log_file ) ) - testdir.makeconftest( + pytester.makeconftest( """ import logging @@ -972,7 +983,7 @@ def pytest_sessionfinish(session, exitstatus): logging.info('sessionfinish') """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*sessionstart*", "*runtestloop*", "*sessionfinish*"]) with open(log_file) as rfh: contents = rfh.read() @@ -981,10 +992,10 @@ def pytest_sessionfinish(session, exitstatus): assert "sessionfinish" in contents -def test_log_in_runtest_logreport(testdir): - log_file = testdir.tmpdir.join("pytest.log").strpath +def test_log_in_runtest_logreport(pytester: Pytester) -> None: + log_file = str(pytester.path.joinpath("pytest.log")) - testdir.makeini( + pytester.makeini( """ [pytest] log_file={} @@ -994,7 +1005,7 @@ def test_log_in_runtest_logreport(testdir): log_file ) ) - testdir.makeconftest( + pytester.makeconftest( """ import logging logger = logging.getLogger(__name__) @@ -1003,29 +1014,29 @@ def pytest_runtest_logreport(report): logger.info("logreport") """ ) - testdir.makepyfile( + pytester.makepyfile( """ def test_first(): assert True """ ) - testdir.runpytest() + pytester.runpytest() with open(log_file) as rfh: contents = rfh.read() assert contents.count("logreport") == 3 -def test_log_set_path(testdir): - report_dir_base = testdir.tmpdir.strpath +def test_log_set_path(pytester: Pytester) -> None: + report_dir_base = str(pytester.path) - testdir.makeini( + pytester.makeini( """ [pytest] log_file_level = DEBUG log_cli=true """ ) - testdir.makeconftest( + pytester.makeconftest( """ import os import pytest @@ -1040,7 +1051,7 @@ def pytest_runtest_setup(item): repr(report_dir_base) ) ) - testdir.makepyfile( + pytester.makepyfile( """ import logging logger = logging.getLogger("testcase-logger") @@ -1053,7 +1064,7 @@ def test_second(): assert True """ ) - testdir.runpytest() + pytester.runpytest() with open(os.path.join(report_dir_base, "test_first")) as rfh: content = rfh.read() assert "message from test 1" in content @@ -1063,10 +1074,10 @@ def test_second(): assert "message from test 2" in content -def test_colored_captured_log(testdir): +def test_colored_captured_log(pytester: Pytester) -> None: """Test that the level names of captured log messages of a failing test are colored.""" - testdir.makepyfile( + pytester.makepyfile( """ import logging @@ -1077,7 +1088,7 @@ def test_foo(): assert False """ ) - result = testdir.runpytest("--log-level=INFO", "--color=yes") + result = pytester.runpytest("--log-level=INFO", "--color=yes") assert result.ret == 1 result.stdout.fnmatch_lines( [ @@ -1087,9 +1098,9 @@ def test_foo(): ) -def test_colored_ansi_esc_caplogtext(testdir): +def test_colored_ansi_esc_caplogtext(pytester: Pytester) -> None: """Make sure that caplog.text does not contain ANSI escape sequences.""" - testdir.makepyfile( + pytester.makepyfile( """ import logging @@ -1100,11 +1111,11 @@ def test_foo(caplog): assert '\x1b' not in caplog.text """ ) - result = testdir.runpytest("--log-level=INFO", "--color=yes") + result = pytester.runpytest("--log-level=INFO", "--color=yes") assert result.ret == 0 -def test_logging_emit_error(testdir: Testdir) -> None: +def test_logging_emit_error(pytester: Pytester) -> None: """An exception raised during emit() should fail the test. The default behavior of logging is to print "Logging error" @@ -1112,7 +1123,7 @@ def test_logging_emit_error(testdir: Testdir) -> None: pytest overrides this behavior to propagate the exception. """ - testdir.makepyfile( + pytester.makepyfile( """ import logging @@ -1120,7 +1131,7 @@ def test_bad_log(): logging.warning('oops', 'first', 2) """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.assert_outcomes(failed=1) result.stdout.fnmatch_lines( [ @@ -1130,10 +1141,10 @@ def test_bad_log(): ) -def test_logging_emit_error_supressed(testdir: Testdir) -> None: +def test_logging_emit_error_supressed(pytester: Pytester) -> None: """If logging is configured to silently ignore errors, pytest doesn't propagate errors either.""" - testdir.makepyfile( + pytester.makepyfile( """ import logging @@ -1142,13 +1153,15 @@ def test_bad_log(monkeypatch): logging.warning('oops', 'first', 2) """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.assert_outcomes(passed=1) -def test_log_file_cli_subdirectories_are_successfully_created(testdir): - path = testdir.makepyfile(""" def test_logger(): pass """) +def test_log_file_cli_subdirectories_are_successfully_created( + pytester: Pytester, +) -> None: + path = pytester.makepyfile(""" def test_logger(): pass """) expected = os.path.join(os.path.dirname(str(path)), "foo", "bar") - result = testdir.runpytest("--log-file=foo/bar/logf.log") + result = pytester.runpytest("--log-file=foo/bar/logf.log") assert "logf.log" in os.listdir(expected) assert result.ret == ExitCode.OK diff --git a/testing/test_cacheprovider.py b/testing/test_cacheprovider.py index ccc7304b02a..7f0827bd488 100644 --- a/testing/test_cacheprovider.py +++ b/testing/test_cacheprovider.py @@ -1,31 +1,32 @@ import os import shutil -import stat import sys - -import py +from pathlib import Path +from typing import List import pytest from _pytest.config import ExitCode +from _pytest.monkeypatch import MonkeyPatch from _pytest.pytester import Pytester -from _pytest.pytester import Testdir pytest_plugins = ("pytester",) class TestNewAPI: - def test_config_cache_makedir(self, testdir): - testdir.makeini("[pytest]") - config = testdir.parseconfigure() + def test_config_cache_makedir(self, pytester: Pytester) -> None: + pytester.makeini("[pytest]") + config = pytester.parseconfigure() + assert config.cache is not None with pytest.raises(ValueError): config.cache.makedir("key/name") p = config.cache.makedir("name") assert p.check() - def test_config_cache_dataerror(self, testdir): - testdir.makeini("[pytest]") - config = testdir.parseconfigure() + def test_config_cache_dataerror(self, pytester: Pytester) -> None: + pytester.makeini("[pytest]") + config = pytester.parseconfigure() + assert config.cache is not None cache = config.cache pytest.raises(TypeError, lambda: cache.set("key/name", cache)) config.cache.set("key/name", 0) @@ -34,39 +35,45 @@ def test_config_cache_dataerror(self, testdir): assert val == -2 @pytest.mark.filterwarnings("ignore:could not create cache path") - def test_cache_writefail_cachfile_silent(self, testdir): - testdir.makeini("[pytest]") - testdir.tmpdir.join(".pytest_cache").write("gone wrong") - config = testdir.parseconfigure() + def test_cache_writefail_cachfile_silent(self, pytester: Pytester) -> None: + pytester.makeini("[pytest]") + pytester.path.joinpath(".pytest_cache").write_text("gone wrong") + config = pytester.parseconfigure() cache = config.cache + assert cache is not None cache.set("test/broken", []) @pytest.mark.skipif(sys.platform.startswith("win"), reason="no chmod on windows") @pytest.mark.filterwarnings( "ignore:could not create cache path:pytest.PytestWarning" ) - def test_cache_writefail_permissions(self, testdir): - testdir.makeini("[pytest]") - cache_dir = str(testdir.tmpdir.ensure_dir(".pytest_cache")) - mode = os.stat(cache_dir)[stat.ST_MODE] - testdir.tmpdir.ensure_dir(".pytest_cache").chmod(0) + def test_cache_writefail_permissions(self, pytester: Pytester) -> None: + pytester.makeini("[pytest]") + cache_dir = pytester.path.joinpath(".pytest_cache") + cache_dir.mkdir() + mode = cache_dir.stat().st_mode + cache_dir.chmod(0) try: - config = testdir.parseconfigure() + config = pytester.parseconfigure() cache = config.cache + assert cache is not None cache.set("test/broken", []) finally: - testdir.tmpdir.ensure_dir(".pytest_cache").chmod(mode) + cache_dir.chmod(mode) @pytest.mark.skipif(sys.platform.startswith("win"), reason="no chmod on windows") @pytest.mark.filterwarnings("default") - def test_cache_failure_warns(self, testdir, monkeypatch): + def test_cache_failure_warns( + self, pytester: Pytester, monkeypatch: MonkeyPatch + ) -> None: monkeypatch.setenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", "1") - cache_dir = str(testdir.tmpdir.ensure_dir(".pytest_cache")) - mode = os.stat(cache_dir)[stat.ST_MODE] - testdir.tmpdir.ensure_dir(".pytest_cache").chmod(0) + cache_dir = pytester.path.joinpath(".pytest_cache") + cache_dir.mkdir() + mode = cache_dir.stat().st_mode + cache_dir.chmod(0) try: - testdir.makepyfile("def test_error(): raise Exception") - result = testdir.runpytest() + pytester.makepyfile("def test_error(): raise Exception") + result = pytester.runpytest() assert result.ret == 1 # warnings from nodeids, lastfailed, and stepwise result.stdout.fnmatch_lines( @@ -81,28 +88,28 @@ def test_cache_failure_warns(self, testdir, monkeypatch): ] ) finally: - testdir.tmpdir.ensure_dir(".pytest_cache").chmod(mode) + cache_dir.chmod(mode) - def test_config_cache(self, testdir): - testdir.makeconftest( + def test_config_cache(self, pytester: Pytester) -> None: + pytester.makeconftest( """ def pytest_configure(config): # see that we get cache information early on assert hasattr(config, "cache") """ ) - testdir.makepyfile( + pytester.makepyfile( """ def test_session(pytestconfig): assert hasattr(pytestconfig, "cache") """ ) - result = testdir.runpytest() + result = pytester.runpytest() assert result.ret == 0 result.stdout.fnmatch_lines(["*1 passed*"]) - def test_cachefuncarg(self, testdir): - testdir.makepyfile( + def test_cachefuncarg(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest def test_cachefuncarg(cache): @@ -114,13 +121,13 @@ def test_cachefuncarg(cache): assert val == [1] """ ) - result = testdir.runpytest() + result = pytester.runpytest() assert result.ret == 0 result.stdout.fnmatch_lines(["*1 passed*"]) - def test_custom_rel_cache_dir(self, testdir): + def test_custom_rel_cache_dir(self, pytester: Pytester) -> None: rel_cache_dir = os.path.join("custom_cache_dir", "subdir") - testdir.makeini( + pytester.makeini( """ [pytest] cache_dir = {cache_dir} @@ -128,14 +135,14 @@ def test_custom_rel_cache_dir(self, testdir): cache_dir=rel_cache_dir ) ) - testdir.makepyfile(test_errored="def test_error():\n assert False") - testdir.runpytest() - assert testdir.tmpdir.join(rel_cache_dir).isdir() + pytester.makepyfile(test_errored="def test_error():\n assert False") + pytester.runpytest() + assert pytester.path.joinpath(rel_cache_dir).is_dir() - def test_custom_abs_cache_dir(self, testdir, tmpdir_factory): + def test_custom_abs_cache_dir(self, pytester: Pytester, tmpdir_factory) -> None: tmp = str(tmpdir_factory.mktemp("tmp")) abs_cache_dir = os.path.join(tmp, "custom_cache_dir") - testdir.makeini( + pytester.makeini( """ [pytest] cache_dir = {cache_dir} @@ -143,13 +150,15 @@ def test_custom_abs_cache_dir(self, testdir, tmpdir_factory): cache_dir=abs_cache_dir ) ) - testdir.makepyfile(test_errored="def test_error():\n assert False") - testdir.runpytest() - assert py.path.local(abs_cache_dir).isdir() + pytester.makepyfile(test_errored="def test_error():\n assert False") + pytester.runpytest() + assert Path(abs_cache_dir).is_dir() - def test_custom_cache_dir_with_env_var(self, testdir, monkeypatch): + def test_custom_cache_dir_with_env_var( + self, pytester: Pytester, monkeypatch: MonkeyPatch + ) -> None: monkeypatch.setenv("env_var", "custom_cache_dir") - testdir.makeini( + pytester.makeini( """ [pytest] cache_dir = {cache_dir} @@ -157,31 +166,33 @@ def test_custom_cache_dir_with_env_var(self, testdir, monkeypatch): cache_dir="$env_var" ) ) - testdir.makepyfile(test_errored="def test_error():\n assert False") - testdir.runpytest() - assert testdir.tmpdir.join("custom_cache_dir").isdir() + pytester.makepyfile(test_errored="def test_error():\n assert False") + pytester.runpytest() + assert pytester.path.joinpath("custom_cache_dir").is_dir() @pytest.mark.parametrize("env", ((), ("TOX_ENV_DIR", "/tox_env_dir"))) -def test_cache_reportheader(env, testdir, monkeypatch): - testdir.makepyfile("""def test_foo(): pass""") +def test_cache_reportheader(env, pytester: Pytester, monkeypatch: MonkeyPatch) -> None: + pytester.makepyfile("""def test_foo(): pass""") if env: monkeypatch.setenv(*env) expected = os.path.join(env[1], ".pytest_cache") else: monkeypatch.delenv("TOX_ENV_DIR", raising=False) expected = ".pytest_cache" - result = testdir.runpytest("-v") + result = pytester.runpytest("-v") result.stdout.fnmatch_lines(["cachedir: %s" % expected]) -def test_cache_reportheader_external_abspath(testdir, tmpdir_factory): +def test_cache_reportheader_external_abspath( + pytester: Pytester, tmpdir_factory +) -> None: external_cache = tmpdir_factory.mktemp( "test_cache_reportheader_external_abspath_abs" ) - testdir.makepyfile("def test_hello(): pass") - testdir.makeini( + pytester.makepyfile("def test_hello(): pass") + pytester.makeini( """ [pytest] cache_dir = {abscache} @@ -189,15 +200,15 @@ def test_cache_reportheader_external_abspath(testdir, tmpdir_factory): abscache=external_cache ) ) - result = testdir.runpytest("-v") + result = pytester.runpytest("-v") result.stdout.fnmatch_lines([f"cachedir: {external_cache}"]) -def test_cache_show(testdir): - result = testdir.runpytest("--cache-show") +def test_cache_show(pytester: Pytester) -> None: + result = pytester.runpytest("--cache-show") assert result.ret == 0 result.stdout.fnmatch_lines(["*cache is empty*"]) - testdir.makeconftest( + pytester.makeconftest( """ def pytest_configure(config): config.cache.set("my/name", [1,2,3]) @@ -208,10 +219,10 @@ def pytest_configure(config): dp.ensure("world") """ ) - result = testdir.runpytest() + result = pytester.runpytest() assert result.ret == 5 # no tests executed - result = testdir.runpytest("--cache-show") + result = pytester.runpytest("--cache-show") result.stdout.fnmatch_lines( [ "*cachedir:*", @@ -228,7 +239,7 @@ def pytest_configure(config): ) assert result.ret == 0 - result = testdir.runpytest("--cache-show", "*/hello") + result = pytester.runpytest("--cache-show", "*/hello") result.stdout.fnmatch_lines( [ "*cachedir:*", @@ -246,25 +257,27 @@ def pytest_configure(config): class TestLastFailed: - def test_lastfailed_usecase(self, testdir, monkeypatch): + def test_lastfailed_usecase( + self, pytester: Pytester, monkeypatch: MonkeyPatch + ) -> None: monkeypatch.setattr("sys.dont_write_bytecode", True) - p = testdir.makepyfile( + p = pytester.makepyfile( """ def test_1(): assert 0 def test_2(): assert 0 def test_3(): assert 1 """ ) - result = testdir.runpytest(str(p)) + result = pytester.runpytest(str(p)) result.stdout.fnmatch_lines(["*2 failed*"]) - p = testdir.makepyfile( + p = pytester.makepyfile( """ def test_1(): assert 1 def test_2(): assert 1 def test_3(): assert 0 """ ) - result = testdir.runpytest(str(p), "--lf") + result = pytester.runpytest(str(p), "--lf") result.stdout.fnmatch_lines( [ "collected 3 items / 1 deselected / 2 selected", @@ -272,7 +285,7 @@ def test_3(): assert 0 "*= 2 passed, 1 deselected in *", ] ) - result = testdir.runpytest(str(p), "--lf") + result = pytester.runpytest(str(p), "--lf") result.stdout.fnmatch_lines( [ "collected 3 items", @@ -280,27 +293,27 @@ def test_3(): assert 0 "*1 failed*2 passed*", ] ) - testdir.tmpdir.join(".pytest_cache").mkdir(".git") - result = testdir.runpytest(str(p), "--lf", "--cache-clear") + pytester.path.joinpath(".pytest_cache", ".git").mkdir(parents=True) + result = pytester.runpytest(str(p), "--lf", "--cache-clear") result.stdout.fnmatch_lines(["*1 failed*2 passed*"]) - assert testdir.tmpdir.join(".pytest_cache", "README.md").isfile() - assert testdir.tmpdir.join(".pytest_cache", ".git").isdir() + assert pytester.path.joinpath(".pytest_cache", "README.md").is_file() + assert pytester.path.joinpath(".pytest_cache", ".git").is_dir() # Run this again to make sure clear-cache is robust if os.path.isdir(".pytest_cache"): shutil.rmtree(".pytest_cache") - result = testdir.runpytest("--lf", "--cache-clear") + result = pytester.runpytest("--lf", "--cache-clear") result.stdout.fnmatch_lines(["*1 failed*2 passed*"]) - def test_failedfirst_order(self, testdir): - testdir.makepyfile( + def test_failedfirst_order(self, pytester: Pytester) -> None: + pytester.makepyfile( test_a="def test_always_passes(): pass", test_b="def test_always_fails(): assert 0", ) - result = testdir.runpytest() + result = pytester.runpytest() # Test order will be collection order; alphabetical result.stdout.fnmatch_lines(["test_a.py*", "test_b.py*"]) - result = testdir.runpytest("--ff") + result = pytester.runpytest("--ff") # Test order will be failing tests first result.stdout.fnmatch_lines( [ @@ -311,40 +324,42 @@ def test_failedfirst_order(self, testdir): ] ) - def test_lastfailed_failedfirst_order(self, testdir): - testdir.makepyfile( + def test_lastfailed_failedfirst_order(self, pytester: Pytester) -> None: + pytester.makepyfile( test_a="def test_always_passes(): assert 1", test_b="def test_always_fails(): assert 0", ) - result = testdir.runpytest() + result = pytester.runpytest() # Test order will be collection order; alphabetical result.stdout.fnmatch_lines(["test_a.py*", "test_b.py*"]) - result = testdir.runpytest("--lf", "--ff") + result = pytester.runpytest("--lf", "--ff") # Test order will be failing tests first result.stdout.fnmatch_lines(["test_b.py*"]) result.stdout.no_fnmatch_line("*test_a.py*") - def test_lastfailed_difference_invocations(self, testdir, monkeypatch): + def test_lastfailed_difference_invocations( + self, pytester: Pytester, monkeypatch: MonkeyPatch + ) -> None: monkeypatch.setattr("sys.dont_write_bytecode", True) - testdir.makepyfile( + pytester.makepyfile( test_a=""" def test_a1(): assert 0 def test_a2(): assert 1 """, test_b="def test_b1(): assert 0", ) - p = testdir.tmpdir.join("test_a.py") - p2 = testdir.tmpdir.join("test_b.py") + p = pytester.path.joinpath("test_a.py") + p2 = pytester.path.joinpath("test_b.py") - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*2 failed*"]) - result = testdir.runpytest("--lf", p2) + result = pytester.runpytest("--lf", p2) result.stdout.fnmatch_lines(["*1 failed*"]) - testdir.makepyfile(test_b="def test_b1(): assert 1") - result = testdir.runpytest("--lf", p2) + pytester.makepyfile(test_b="def test_b1(): assert 1") + result = pytester.runpytest("--lf", p2) result.stdout.fnmatch_lines(["*1 passed*"]) - result = testdir.runpytest("--lf", p) + result = pytester.runpytest("--lf", p) result.stdout.fnmatch_lines( [ "collected 2 items / 1 deselected / 1 selected", @@ -353,21 +368,23 @@ def test_a2(): assert 1 ] ) - def test_lastfailed_usecase_splice(self, testdir, monkeypatch): + def test_lastfailed_usecase_splice( + self, pytester: Pytester, monkeypatch: MonkeyPatch + ) -> None: monkeypatch.setattr("sys.dont_write_bytecode", True) - testdir.makepyfile( + pytester.makepyfile( "def test_1(): assert 0", test_something="def test_2(): assert 0" ) - p2 = testdir.tmpdir.join("test_something.py") - result = testdir.runpytest() + p2 = pytester.path.joinpath("test_something.py") + result = pytester.runpytest() result.stdout.fnmatch_lines(["*2 failed*"]) - result = testdir.runpytest("--lf", p2) + result = pytester.runpytest("--lf", p2) result.stdout.fnmatch_lines(["*1 failed*"]) - result = testdir.runpytest("--lf") + result = pytester.runpytest("--lf") result.stdout.fnmatch_lines(["*2 failed*"]) - def test_lastfailed_xpass(self, testdir): - testdir.inline_runsource( + def test_lastfailed_xpass(self, pytester: Pytester) -> None: + pytester.inline_runsource( """ import pytest @pytest.mark.xfail @@ -375,15 +392,16 @@ def test_hello(): assert 1 """ ) - config = testdir.parseconfigure() + config = pytester.parseconfigure() + assert config.cache is not None lastfailed = config.cache.get("cache/lastfailed", -1) assert lastfailed == -1 - def test_non_serializable_parametrize(self, testdir): + def test_non_serializable_parametrize(self, pytester: Pytester) -> None: """Test that failed parametrized tests with unmarshable parameters don't break pytest-cache. """ - testdir.makepyfile( + pytester.makepyfile( r""" import pytest @@ -394,26 +412,26 @@ def test_fail(val): assert False """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*1 failed in*"]) - def test_terminal_report_lastfailed(self, testdir): - test_a = testdir.makepyfile( + def test_terminal_report_lastfailed(self, pytester: Pytester) -> None: + test_a = pytester.makepyfile( test_a=""" def test_a1(): pass def test_a2(): pass """ ) - test_b = testdir.makepyfile( + test_b = pytester.makepyfile( test_b=""" def test_b1(): assert 0 def test_b2(): assert 0 """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["collected 4 items", "*2 failed, 2 passed in*"]) - result = testdir.runpytest("--lf") + result = pytester.runpytest("--lf") result.stdout.fnmatch_lines( [ "collected 2 items", @@ -422,7 +440,7 @@ def test_b2(): assert 0 ] ) - result = testdir.runpytest(test_a, "--lf") + result = pytester.runpytest(test_a, "--lf") result.stdout.fnmatch_lines( [ "collected 2 items", @@ -431,7 +449,7 @@ def test_b2(): assert 0 ] ) - result = testdir.runpytest(test_b, "--lf") + result = pytester.runpytest(test_b, "--lf") result.stdout.fnmatch_lines( [ "collected 2 items", @@ -440,7 +458,7 @@ def test_b2(): assert 0 ] ) - result = testdir.runpytest("test_b.py::test_b1", "--lf") + result = pytester.runpytest("test_b.py::test_b1", "--lf") result.stdout.fnmatch_lines( [ "collected 1 item", @@ -449,17 +467,17 @@ def test_b2(): assert 0 ] ) - def test_terminal_report_failedfirst(self, testdir): - testdir.makepyfile( + def test_terminal_report_failedfirst(self, pytester: Pytester) -> None: + pytester.makepyfile( test_a=""" def test_a1(): assert 0 def test_a2(): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["collected 2 items", "*1 failed, 1 passed in*"]) - result = testdir.runpytest("--ff") + result = pytester.runpytest("--ff") result.stdout.fnmatch_lines( [ "collected 2 items", @@ -468,9 +486,11 @@ def test_a2(): pass ] ) - def test_lastfailed_collectfailure(self, testdir, monkeypatch): + def test_lastfailed_collectfailure( + self, pytester: Pytester, monkeypatch: MonkeyPatch + ) -> None: - testdir.makepyfile( + pytester.makepyfile( test_maybe=""" import os env = os.environ @@ -485,8 +505,9 @@ def rlf(fail_import, fail_run): monkeypatch.setenv("FAILIMPORT", str(fail_import)) monkeypatch.setenv("FAILTEST", str(fail_run)) - testdir.runpytest("-q") - config = testdir.parseconfigure() + pytester.runpytest("-q") + config = pytester.parseconfigure() + assert config.cache is not None lastfailed = config.cache.get("cache/lastfailed", -1) return lastfailed @@ -499,8 +520,10 @@ def rlf(fail_import, fail_run): lastfailed = rlf(fail_import=0, fail_run=1) assert list(lastfailed) == ["test_maybe.py::test_hello"] - def test_lastfailed_failure_subset(self, testdir, monkeypatch): - testdir.makepyfile( + def test_lastfailed_failure_subset( + self, pytester: Pytester, monkeypatch: MonkeyPatch + ) -> None: + pytester.makepyfile( test_maybe=""" import os env = os.environ @@ -511,7 +534,7 @@ def test_hello(): """ ) - testdir.makepyfile( + pytester.makepyfile( test_maybe2=""" import os env = os.environ @@ -530,8 +553,9 @@ def rlf(fail_import, fail_run, args=()): monkeypatch.setenv("FAILIMPORT", str(fail_import)) monkeypatch.setenv("FAILTEST", str(fail_run)) - result = testdir.runpytest("-q", "--lf", *args) - config = testdir.parseconfigure() + result = pytester.runpytest("-q", "--lf", *args) + config = pytester.parseconfigure() + assert config.cache is not None lastfailed = config.cache.get("cache/lastfailed", -1) return result, lastfailed @@ -552,61 +576,63 @@ def rlf(fail_import, fail_run, args=()): assert list(lastfailed) == ["test_maybe.py"] result.stdout.fnmatch_lines(["*2 passed*"]) - def test_lastfailed_creates_cache_when_needed(self, testdir): + def test_lastfailed_creates_cache_when_needed(self, pytester: Pytester) -> None: # Issue #1342 - testdir.makepyfile(test_empty="") - testdir.runpytest("-q", "--lf") + pytester.makepyfile(test_empty="") + pytester.runpytest("-q", "--lf") assert not os.path.exists(".pytest_cache/v/cache/lastfailed") - testdir.makepyfile(test_successful="def test_success():\n assert True") - testdir.runpytest("-q", "--lf") + pytester.makepyfile(test_successful="def test_success():\n assert True") + pytester.runpytest("-q", "--lf") assert not os.path.exists(".pytest_cache/v/cache/lastfailed") - testdir.makepyfile(test_errored="def test_error():\n assert False") - testdir.runpytest("-q", "--lf") + pytester.makepyfile(test_errored="def test_error():\n assert False") + pytester.runpytest("-q", "--lf") assert os.path.exists(".pytest_cache/v/cache/lastfailed") - def test_xfail_not_considered_failure(self, testdir): - testdir.makepyfile( + def test_xfail_not_considered_failure(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @pytest.mark.xfail def test(): assert 0 """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*1 xfailed*"]) - assert self.get_cached_last_failed(testdir) == [] + assert self.get_cached_last_failed(pytester) == [] - def test_xfail_strict_considered_failure(self, testdir): - testdir.makepyfile( + def test_xfail_strict_considered_failure(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @pytest.mark.xfail(strict=True) def test(): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*1 failed*"]) - assert self.get_cached_last_failed(testdir) == [ + assert self.get_cached_last_failed(pytester) == [ "test_xfail_strict_considered_failure.py::test" ] @pytest.mark.parametrize("mark", ["mark.xfail", "mark.skip"]) - def test_failed_changed_to_xfail_or_skip(self, testdir, mark): - testdir.makepyfile( + def test_failed_changed_to_xfail_or_skip( + self, pytester: Pytester, mark: str + ) -> None: + pytester.makepyfile( """ import pytest def test(): assert 0 """ ) - result = testdir.runpytest() - assert self.get_cached_last_failed(testdir) == [ + result = pytester.runpytest() + assert self.get_cached_last_failed(pytester) == [ "test_failed_changed_to_xfail_or_skip.py::test" ] assert result.ret == 1 - testdir.makepyfile( + pytester.makepyfile( """ import pytest @pytest.{mark} @@ -615,66 +641,69 @@ def test(): assert 0 mark=mark ) ) - result = testdir.runpytest() + result = pytester.runpytest() assert result.ret == 0 - assert self.get_cached_last_failed(testdir) == [] + assert self.get_cached_last_failed(pytester) == [] assert result.ret == 0 @pytest.mark.parametrize("quiet", [True, False]) @pytest.mark.parametrize("opt", ["--ff", "--lf"]) - def test_lf_and_ff_prints_no_needless_message(self, quiet, opt, testdir): + def test_lf_and_ff_prints_no_needless_message( + self, quiet: bool, opt: str, pytester: Pytester + ) -> None: # Issue 3853 - testdir.makepyfile("def test(): assert 0") + pytester.makepyfile("def test(): assert 0") args = [opt] if quiet: args.append("-q") - result = testdir.runpytest(*args) + result = pytester.runpytest(*args) result.stdout.no_fnmatch_line("*run all*") - result = testdir.runpytest(*args) + result = pytester.runpytest(*args) if quiet: result.stdout.no_fnmatch_line("*run all*") else: assert "rerun previous" in result.stdout.str() - def get_cached_last_failed(self, testdir): - config = testdir.parseconfigure() + def get_cached_last_failed(self, pytester: Pytester) -> List[str]: + config = pytester.parseconfigure() + assert config.cache is not None return sorted(config.cache.get("cache/lastfailed", {})) - def test_cache_cumulative(self, testdir): + def test_cache_cumulative(self, pytester: Pytester) -> None: """Test workflow where user fixes errors gradually file by file using --lf.""" # 1. initial run - test_bar = testdir.makepyfile( + test_bar = pytester.makepyfile( test_bar=""" def test_bar_1(): pass def test_bar_2(): assert 0 """ ) - test_foo = testdir.makepyfile( + test_foo = pytester.makepyfile( test_foo=""" def test_foo_3(): pass def test_foo_4(): assert 0 """ ) - testdir.runpytest() - assert self.get_cached_last_failed(testdir) == [ + pytester.runpytest() + assert self.get_cached_last_failed(pytester) == [ "test_bar.py::test_bar_2", "test_foo.py::test_foo_4", ] # 2. fix test_bar_2, run only test_bar.py - testdir.makepyfile( + pytester.makepyfile( test_bar=""" def test_bar_1(): pass def test_bar_2(): pass """ ) - result = testdir.runpytest(test_bar) + result = pytester.runpytest(test_bar) result.stdout.fnmatch_lines(["*2 passed*"]) # ensure cache does not forget that test_foo_4 failed once before - assert self.get_cached_last_failed(testdir) == ["test_foo.py::test_foo_4"] + assert self.get_cached_last_failed(pytester) == ["test_foo.py::test_foo_4"] - result = testdir.runpytest("--last-failed") + result = pytester.runpytest("--last-failed") result.stdout.fnmatch_lines( [ "collected 1 item", @@ -682,16 +711,16 @@ def test_bar_2(): pass "*= 1 failed in *", ] ) - assert self.get_cached_last_failed(testdir) == ["test_foo.py::test_foo_4"] + assert self.get_cached_last_failed(pytester) == ["test_foo.py::test_foo_4"] # 3. fix test_foo_4, run only test_foo.py - test_foo = testdir.makepyfile( + test_foo = pytester.makepyfile( test_foo=""" def test_foo_3(): pass def test_foo_4(): pass """ ) - result = testdir.runpytest(test_foo, "--last-failed") + result = pytester.runpytest(test_foo, "--last-failed") result.stdout.fnmatch_lines( [ "collected 2 items / 1 deselected / 1 selected", @@ -699,29 +728,31 @@ def test_foo_4(): pass "*= 1 passed, 1 deselected in *", ] ) - assert self.get_cached_last_failed(testdir) == [] + assert self.get_cached_last_failed(pytester) == [] - result = testdir.runpytest("--last-failed") + result = pytester.runpytest("--last-failed") result.stdout.fnmatch_lines(["*4 passed*"]) - assert self.get_cached_last_failed(testdir) == [] + assert self.get_cached_last_failed(pytester) == [] - def test_lastfailed_no_failures_behavior_all_passed(self, testdir): - testdir.makepyfile( + def test_lastfailed_no_failures_behavior_all_passed( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( """ def test_1(): pass def test_2(): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*2 passed*"]) - result = testdir.runpytest("--lf") + result = pytester.runpytest("--lf") result.stdout.fnmatch_lines(["*2 passed*"]) - result = testdir.runpytest("--lf", "--lfnf", "all") + result = pytester.runpytest("--lf", "--lfnf", "all") result.stdout.fnmatch_lines(["*2 passed*"]) # Ensure the list passed to pytest_deselected is a copy, # and not a reference which is cleared right after. - testdir.makeconftest( + pytester.makeconftest( """ deselected = [] @@ -734,7 +765,7 @@ def pytest_sessionfinish(): """ ) - result = testdir.runpytest("--lf", "--lfnf", "none") + result = pytester.runpytest("--lf", "--lfnf", "none") result.stdout.fnmatch_lines( [ "collected 2 items / 2 deselected", @@ -745,26 +776,28 @@ def pytest_sessionfinish(): ) assert result.ret == ExitCode.NO_TESTS_COLLECTED - def test_lastfailed_no_failures_behavior_empty_cache(self, testdir): - testdir.makepyfile( + def test_lastfailed_no_failures_behavior_empty_cache( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( """ def test_1(): pass def test_2(): assert 0 """ ) - result = testdir.runpytest("--lf", "--cache-clear") + result = pytester.runpytest("--lf", "--cache-clear") result.stdout.fnmatch_lines(["*1 failed*1 passed*"]) - result = testdir.runpytest("--lf", "--cache-clear", "--lfnf", "all") + result = pytester.runpytest("--lf", "--cache-clear", "--lfnf", "all") result.stdout.fnmatch_lines(["*1 failed*1 passed*"]) - result = testdir.runpytest("--lf", "--cache-clear", "--lfnf", "none") + result = pytester.runpytest("--lf", "--cache-clear", "--lfnf", "none") result.stdout.fnmatch_lines(["*2 desel*"]) - def test_lastfailed_skip_collection(self, testdir): + def test_lastfailed_skip_collection(self, pytester: Pytester) -> None: """ Test --lf behavior regarding skipping collection of files that are not marked as failed in the cache (#5172). """ - testdir.makepyfile( + pytester.makepyfile( **{ "pkg1/test_1.py": """ import pytest @@ -782,10 +815,10 @@ def test_1(i): } ) # first run: collects 8 items (test_1: 3, test_2: 5) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["collected 8 items", "*2 failed*6 passed*"]) # second run: collects only 5 items from test_2, because all tests from test_1 have passed - result = testdir.runpytest("--lf") + result = pytester.runpytest("--lf") result.stdout.fnmatch_lines( [ "collected 2 items", @@ -795,14 +828,14 @@ def test_1(i): ) # add another file and check if message is correct when skipping more than 1 file - testdir.makepyfile( + pytester.makepyfile( **{ "pkg1/test_3.py": """ def test_3(): pass """ } ) - result = testdir.runpytest("--lf") + result = pytester.runpytest("--lf") result.stdout.fnmatch_lines( [ "collected 2 items", @@ -811,18 +844,20 @@ def test_3(): pass ] ) - def test_lastfailed_with_known_failures_not_being_selected(self, testdir): - testdir.makepyfile( + def test_lastfailed_with_known_failures_not_being_selected( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( **{ "pkg1/test_1.py": """def test_1(): assert 0""", "pkg1/test_2.py": """def test_2(): pass""", } ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["collected 2 items", "* 1 failed, 1 passed in *"]) - py.path.local("pkg1/test_1.py").remove() - result = testdir.runpytest("--lf") + Path("pkg1/test_1.py").unlink() + result = pytester.runpytest("--lf") result.stdout.fnmatch_lines( [ "collected 1 item", @@ -832,8 +867,8 @@ def test_lastfailed_with_known_failures_not_being_selected(self, testdir): ) # Recreate file with known failure. - testdir.makepyfile(**{"pkg1/test_1.py": """def test_1(): assert 0"""}) - result = testdir.runpytest("--lf") + pytester.makepyfile(**{"pkg1/test_1.py": """def test_1(): assert 0"""}) + result = pytester.runpytest("--lf") result.stdout.fnmatch_lines( [ "collected 1 item", @@ -843,8 +878,8 @@ def test_lastfailed_with_known_failures_not_being_selected(self, testdir): ) # Remove/rename test: collects the file again. - testdir.makepyfile(**{"pkg1/test_1.py": """def test_renamed(): assert 0"""}) - result = testdir.runpytest("--lf", "-rf") + pytester.makepyfile(**{"pkg1/test_1.py": """def test_renamed(): assert 0"""}) + result = pytester.runpytest("--lf", "-rf") result.stdout.fnmatch_lines( [ "collected 2 items", @@ -856,7 +891,7 @@ def test_lastfailed_with_known_failures_not_being_selected(self, testdir): ] ) - result = testdir.runpytest("--lf", "--co") + result = pytester.runpytest("--lf", "--co") result.stdout.fnmatch_lines( [ "collected 1 item", @@ -867,13 +902,13 @@ def test_lastfailed_with_known_failures_not_being_selected(self, testdir): ] ) - def test_lastfailed_args_with_deselected(self, testdir: Testdir) -> None: + def test_lastfailed_args_with_deselected(self, pytester: Pytester) -> None: """Test regression with --lf running into NoMatch error. This was caused by it not collecting (non-failed) nodes given as arguments. """ - testdir.makepyfile( + pytester.makepyfile( **{ "pkg1/test_1.py": """ def test_pass(): pass @@ -881,11 +916,11 @@ def test_fail(): assert 0 """, } ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["collected 2 items", "* 1 failed, 1 passed in *"]) assert result.ret == 1 - result = testdir.runpytest("pkg1/test_1.py::test_pass", "--lf", "--co") + result = pytester.runpytest("pkg1/test_1.py::test_pass", "--lf", "--co") assert result.ret == 0 result.stdout.fnmatch_lines( [ @@ -898,7 +933,7 @@ def test_fail(): assert 0 consecutive=True, ) - result = testdir.runpytest( + result = pytester.runpytest( "pkg1/test_1.py::test_pass", "pkg1/test_1.py::test_fail", "--lf", "--co" ) assert result.ret == 0 @@ -913,9 +948,9 @@ def test_fail(): assert 0 ], ) - def test_lastfailed_with_class_items(self, testdir: Testdir) -> None: + def test_lastfailed_with_class_items(self, pytester: Pytester) -> None: """Test regression with --lf deselecting whole classes.""" - testdir.makepyfile( + pytester.makepyfile( **{ "pkg1/test_1.py": """ class TestFoo: @@ -926,11 +961,11 @@ def test_other(): assert 0 """, } ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["collected 3 items", "* 2 failed, 1 passed in *"]) assert result.ret == 1 - result = testdir.runpytest("--lf", "--co") + result = pytester.runpytest("--lf", "--co") assert result.ret == 0 result.stdout.fnmatch_lines( [ @@ -947,8 +982,8 @@ def test_other(): assert 0 consecutive=True, ) - def test_lastfailed_with_all_filtered(self, testdir: Testdir) -> None: - testdir.makepyfile( + def test_lastfailed_with_all_filtered(self, pytester: Pytester) -> None: + pytester.makepyfile( **{ "pkg1/test_1.py": """ def test_fail(): assert 0 @@ -956,19 +991,19 @@ def test_pass(): pass """, } ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["collected 2 items", "* 1 failed, 1 passed in *"]) assert result.ret == 1 # Remove known failure. - testdir.makepyfile( + pytester.makepyfile( **{ "pkg1/test_1.py": """ def test_pass(): pass """, } ) - result = testdir.runpytest("--lf", "--co") + result = pytester.runpytest("--lf", "--co") result.stdout.fnmatch_lines( [ "collected 1 item", @@ -1015,8 +1050,8 @@ def test_packages(self, pytester: Pytester) -> None: class TestNewFirst: - def test_newfirst_usecase(self, testdir): - testdir.makepyfile( + def test_newfirst_usecase(self, pytester: Pytester, testdir) -> None: + pytester.makepyfile( **{ "test_1/test_1.py": """ def test_1(): assert 1 @@ -1026,24 +1061,24 @@ def test_1(): assert 1 """, } ) - testdir.tmpdir.join("test_1/test_1.py").setmtime(1) - result = testdir.runpytest("-v") + p1 = pytester.path.joinpath("test_1/test_1.py") + os.utime(p1, ns=(p1.stat().st_atime_ns, int(1e9))) + + result = pytester.runpytest("-v") result.stdout.fnmatch_lines( ["*test_1/test_1.py::test_1 PASSED*", "*test_2/test_2.py::test_1 PASSED*"] ) - result = testdir.runpytest("-v", "--nf") + result = pytester.runpytest("-v", "--nf") result.stdout.fnmatch_lines( ["*test_2/test_2.py::test_1 PASSED*", "*test_1/test_1.py::test_1 PASSED*"] ) - testdir.tmpdir.join("test_1/test_1.py").write( - "def test_1(): assert 1\n" "def test_2(): assert 1\n" - ) - testdir.tmpdir.join("test_1/test_1.py").setmtime(1) + p1.write_text("def test_1(): assert 1\n" "def test_2(): assert 1\n") + os.utime(p1, ns=(p1.stat().st_atime_ns, int(1e9))) - result = testdir.runpytest("--nf", "--collect-only", "-q") + result = pytester.runpytest("--nf", "--collect-only", "-q") result.stdout.fnmatch_lines( [ "test_1/test_1.py::test_2", @@ -1053,15 +1088,15 @@ def test_1(): assert 1 ) # Newest first with (plugin) pytest_collection_modifyitems hook. - testdir.makepyfile( + pytester.makepyfile( myplugin=""" def pytest_collection_modifyitems(items): items[:] = sorted(items, key=lambda item: item.nodeid) print("new_items:", [x.nodeid for x in items]) """ ) - testdir.syspathinsert() - result = testdir.runpytest("--nf", "-p", "myplugin", "--collect-only", "-q") + pytester.syspathinsert() + result = pytester.runpytest("--nf", "-p", "myplugin", "--collect-only", "-q") result.stdout.fnmatch_lines( [ "new_items: *test_1.py*test_1.py*test_2.py*", @@ -1071,8 +1106,8 @@ def pytest_collection_modifyitems(items): ] ) - def test_newfirst_parametrize(self, testdir): - testdir.makepyfile( + def test_newfirst_parametrize(self, pytester: Pytester) -> None: + pytester.makepyfile( **{ "test_1/test_1.py": """ import pytest @@ -1087,9 +1122,10 @@ def test_1(num): assert num } ) - testdir.tmpdir.join("test_1/test_1.py").setmtime(1) + p1 = pytester.path.joinpath("test_1/test_1.py") + os.utime(p1, ns=(p1.stat().st_atime_ns, int(1e9))) - result = testdir.runpytest("-v") + result = pytester.runpytest("-v") result.stdout.fnmatch_lines( [ "*test_1/test_1.py::test_1[1*", @@ -1099,7 +1135,7 @@ def test_1(num): assert num ] ) - result = testdir.runpytest("-v", "--nf") + result = pytester.runpytest("-v", "--nf") result.stdout.fnmatch_lines( [ "*test_2/test_2.py::test_1[1*", @@ -1109,20 +1145,20 @@ def test_1(num): assert num ] ) - testdir.tmpdir.join("test_1/test_1.py").write( + p1.write_text( "import pytest\n" "@pytest.mark.parametrize('num', [1, 2, 3])\n" "def test_1(num): assert num\n" ) - testdir.tmpdir.join("test_1/test_1.py").setmtime(1) + os.utime(p1, ns=(p1.stat().st_atime_ns, int(1e9))) # Running only a subset does not forget about existing ones. - result = testdir.runpytest("-v", "--nf", "test_2/test_2.py") + result = pytester.runpytest("-v", "--nf", "test_2/test_2.py") result.stdout.fnmatch_lines( ["*test_2/test_2.py::test_1[1*", "*test_2/test_2.py::test_1[2*"] ) - result = testdir.runpytest("-v", "--nf") + result = pytester.runpytest("-v", "--nf") result.stdout.fnmatch_lines( [ "*test_1/test_1.py::test_1[3*", @@ -1135,27 +1171,28 @@ def test_1(num): assert num class TestReadme: - def check_readme(self, testdir): - config = testdir.parseconfigure() + def check_readme(self, pytester: Pytester) -> bool: + config = pytester.parseconfigure() + assert config.cache is not None readme = config.cache._cachedir.joinpath("README.md") return readme.is_file() - def test_readme_passed(self, testdir): - testdir.makepyfile("def test_always_passes(): pass") - testdir.runpytest() - assert self.check_readme(testdir) is True + def test_readme_passed(self, pytester: Pytester) -> None: + pytester.makepyfile("def test_always_passes(): pass") + pytester.runpytest() + assert self.check_readme(pytester) is True - def test_readme_failed(self, testdir): - testdir.makepyfile("def test_always_fails(): assert 0") - testdir.runpytest() - assert self.check_readme(testdir) is True + def test_readme_failed(self, pytester: Pytester) -> None: + pytester.makepyfile("def test_always_fails(): assert 0") + pytester.runpytest() + assert self.check_readme(pytester) is True -def test_gitignore(testdir): +def test_gitignore(pytester: Pytester) -> None: """Ensure we automatically create .gitignore file in the pytest_cache directory (#3286).""" from _pytest.cacheprovider import Cache - config = testdir.parseconfig() + config = pytester.parseconfig() cache = Cache.for_config(config, _ispytest=True) cache.set("foo", "bar") msg = "# Created by pytest automatically.\n*\n" @@ -1168,16 +1205,16 @@ def test_gitignore(testdir): assert gitignore_path.read_text(encoding="UTF-8") == "custom" -def test_does_not_create_boilerplate_in_existing_dirs(testdir): +def test_does_not_create_boilerplate_in_existing_dirs(pytester: Pytester) -> None: from _pytest.cacheprovider import Cache - testdir.makeini( + pytester.makeini( """ [pytest] cache_dir = . """ ) - config = testdir.parseconfig() + config = pytester.parseconfig() cache = Cache.for_config(config, _ispytest=True) cache.set("foo", "bar") @@ -1186,12 +1223,12 @@ def test_does_not_create_boilerplate_in_existing_dirs(testdir): assert not os.path.exists("README.md") -def test_cachedir_tag(testdir): +def test_cachedir_tag(pytester: Pytester) -> None: """Ensure we automatically create CACHEDIR.TAG file in the pytest_cache directory (#4278).""" from _pytest.cacheprovider import Cache from _pytest.cacheprovider import CACHEDIR_TAG_CONTENT - config = testdir.parseconfig() + config = pytester.parseconfig() cache = Cache.for_config(config, _ispytest=True) cache.set("foo", "bar") cachedir_tag_path = cache._cachedir.joinpath("CACHEDIR.TAG") diff --git a/testing/test_conftest.py b/testing/test_conftest.py index 36e83191bcd..80f2a6d0bc0 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -8,8 +8,6 @@ from typing import List from typing import Optional -import py - import pytest from _pytest.config import ExitCode from _pytest.config import PytestPluginManager @@ -93,9 +91,9 @@ def test_value_access_with_confmod(self, basedir: Path) -> None: conftest = ConftestWithSetinitial(startdir) mod, value = conftest._rget_with_confmod("a", startdir, importmode="prepend") assert value == 1.5 - path = py.path.local(mod.__file__) - assert path.dirpath() == basedir / "adir" / "b" - assert path.purebasename.startswith("conftest") + path = Path(mod.__file__) + assert path.parent == basedir / "adir" / "b" + assert path.stem == "conftest" def test_conftest_in_nonpkg_with_init(tmp_path: Path, _sys_snapshot) -> None: @@ -361,11 +359,10 @@ def impct(p, importmode): def test_fixture_dependency(pytester: Pytester) -> None: - ct1 = pytester.makeconftest("") - ct1 = pytester.makepyfile("__init__.py") - ct1.write_text("") + pytester.makeconftest("") + pytester.path.joinpath("__init__.py").touch() sub = pytester.mkdir("sub") - sub.joinpath("__init__.py").write_text("") + sub.joinpath("__init__.py").touch() sub.joinpath("conftest.py").write_text( textwrap.dedent( """\ @@ -387,7 +384,7 @@ def bar(foo): ) subsub = sub.joinpath("subsub") subsub.mkdir() - subsub.joinpath("__init__.py").write_text("") + subsub.joinpath("__init__.py").touch() subsub.joinpath("test_bar.py").write_text( textwrap.dedent( """\ @@ -525,8 +522,8 @@ def test_parsefactories_relative_node_ids( """#616""" dirs = self._setup_tree(pytester) print("pytest run in cwd: %s" % (dirs[chdir].relative_to(pytester.path))) - print("pytestarg : %s" % (testarg)) - print("expected pass : %s" % (expect_ntests_passed)) + print("pytestarg : %s" % testarg) + print("expected pass : %s" % expect_ntests_passed) os.chdir(dirs[chdir]) reprec = pytester.inline_run(testarg, "-q", "--traceconfig") reprec.assertoutcome(passed=expect_ntests_passed) diff --git a/testing/test_monkeypatch.py b/testing/test_monkeypatch.py index c20ff7480a8..0b97a0e5adb 100644 --- a/testing/test_monkeypatch.py +++ b/testing/test_monkeypatch.py @@ -2,15 +2,14 @@ import re import sys import textwrap +from pathlib import Path from typing import Dict from typing import Generator from typing import Type -import py - import pytest from _pytest.monkeypatch import MonkeyPatch -from _pytest.pytester import Testdir +from _pytest.pytester import Pytester @pytest.fixture @@ -233,8 +232,8 @@ def test_setenv_prepend() -> None: assert "XYZ123" not in os.environ -def test_monkeypatch_plugin(testdir: Testdir) -> None: - reprec = testdir.inline_runsource( +def test_monkeypatch_plugin(pytester: Pytester) -> None: + reprec = pytester.inline_runsource( """ def test_method(monkeypatch): assert monkeypatch.__class__.__name__ == "MonkeyPatch" @@ -268,33 +267,33 @@ def test_syspath_prepend_double_undo(mp: MonkeyPatch) -> None: sys.path[:] = old_syspath -def test_chdir_with_path_local(mp: MonkeyPatch, tmpdir: py.path.local) -> None: - mp.chdir(tmpdir) - assert os.getcwd() == tmpdir.strpath +def test_chdir_with_path_local(mp: MonkeyPatch, tmp_path: Path) -> None: + mp.chdir(tmp_path) + assert os.getcwd() == str(tmp_path) -def test_chdir_with_str(mp: MonkeyPatch, tmpdir: py.path.local) -> None: - mp.chdir(tmpdir.strpath) - assert os.getcwd() == tmpdir.strpath +def test_chdir_with_str(mp: MonkeyPatch, tmp_path: Path) -> None: + mp.chdir(str(tmp_path)) + assert os.getcwd() == str(tmp_path) -def test_chdir_undo(mp: MonkeyPatch, tmpdir: py.path.local) -> None: +def test_chdir_undo(mp: MonkeyPatch, tmp_path: Path) -> None: cwd = os.getcwd() - mp.chdir(tmpdir) + mp.chdir(tmp_path) mp.undo() assert os.getcwd() == cwd -def test_chdir_double_undo(mp: MonkeyPatch, tmpdir: py.path.local) -> None: - mp.chdir(tmpdir.strpath) +def test_chdir_double_undo(mp: MonkeyPatch, tmp_path: Path) -> None: + mp.chdir(str(tmp_path)) mp.undo() - tmpdir.chdir() + os.chdir(tmp_path) mp.undo() - assert os.getcwd() == tmpdir.strpath + assert os.getcwd() == str(tmp_path) -def test_issue185_time_breaks(testdir: Testdir) -> None: - testdir.makepyfile( +def test_issue185_time_breaks(pytester: Pytester) -> None: + pytester.makepyfile( """ import time def test_m(monkeypatch): @@ -303,7 +302,7 @@ def f(): monkeypatch.setattr(time, "time", f) """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( """ *1 passed* @@ -311,9 +310,9 @@ def f(): ) -def test_importerror(testdir: Testdir) -> None: - p = testdir.mkpydir("package") - p.join("a.py").write( +def test_importerror(pytester: Pytester) -> None: + p = pytester.mkpydir("package") + p.joinpath("a.py").write_text( textwrap.dedent( """\ import doesnotexist @@ -322,7 +321,7 @@ def test_importerror(testdir: Testdir) -> None: """ ) ) - testdir.tmpdir.join("test_importerror.py").write( + pytester.path.joinpath("test_importerror.py").write_text( textwrap.dedent( """\ def test_importerror(monkeypatch): @@ -330,7 +329,7 @@ def test_importerror(monkeypatch): """ ) ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( """ *import error in package.a: No module named 'doesnotexist'* @@ -420,16 +419,18 @@ class A: def test_syspath_prepend_with_namespace_packages( - testdir: Testdir, monkeypatch: MonkeyPatch + pytester: Pytester, monkeypatch: MonkeyPatch ) -> None: for dirname in "hello", "world": - d = testdir.mkdir(dirname) - ns = d.mkdir("ns_pkg") - ns.join("__init__.py").write( + d = pytester.mkdir(dirname) + ns = d.joinpath("ns_pkg") + ns.mkdir() + ns.joinpath("__init__.py").write_text( "__import__('pkg_resources').declare_namespace(__name__)" ) - lib = ns.mkdir(dirname) - lib.join("__init__.py").write("def check(): return %r" % dirname) + lib = ns.joinpath(dirname) + lib.mkdir() + lib.joinpath("__init__.py").write_text("def check(): return %r" % dirname) monkeypatch.syspath_prepend("hello") import ns_pkg.hello @@ -446,8 +447,7 @@ def test_syspath_prepend_with_namespace_packages( assert ns_pkg.world.check() == "world" # Should invalidate caches via importlib.invalidate_caches. - tmpdir = testdir.tmpdir - modules_tmpdir = tmpdir.mkdir("modules_tmpdir") + modules_tmpdir = pytester.mkdir("modules_tmpdir") monkeypatch.syspath_prepend(str(modules_tmpdir)) - modules_tmpdir.join("main_app.py").write("app = True") + modules_tmpdir.joinpath("main_app.py").write_text("app = True") from main_app import app # noqa: F401 diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py index 89f10a7db64..a5282a50795 100644 --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -1,13 +1,17 @@ import os +import shutil import sys import types from typing import List import pytest +from _pytest.config import Config from _pytest.config import ExitCode from _pytest.config import PytestPluginManager from _pytest.config.exceptions import UsageError from _pytest.main import Session +from _pytest.monkeypatch import MonkeyPatch +from _pytest.pathlib import import_path from _pytest.pytester import Pytester @@ -18,7 +22,7 @@ def pytestpm() -> PytestPluginManager: class TestPytestPluginInteractions: def test_addhooks_conftestplugin( - self, pytester: Pytester, _config_for_test + self, pytester: Pytester, _config_for_test: Config ) -> None: pytester.makepyfile( newhooks=""" @@ -45,15 +49,15 @@ def pytest_myhook(xyz): res = config.hook.pytest_myhook(xyz=10) assert res == [11] - def test_addhooks_nohooks(self, testdir): - testdir.makeconftest( + def test_addhooks_nohooks(self, pytester: Pytester) -> None: + pytester.makeconftest( """ import sys def pytest_addhooks(pluginmanager): pluginmanager.add_hookspecs(sys) """ ) - res = testdir.runpytest() + res = pytester.runpytest() assert res.ret != 0 res.stderr.fnmatch_lines(["*did not find*sys*"]) @@ -70,8 +74,8 @@ def pytest_addoption(parser): config.pluginmanager._importconftest(p, importmode="prepend") assert config.option.test123 - def test_configure(self, testdir): - config = testdir.parseconfig() + def test_configure(self, pytester: Pytester) -> None: + config = pytester.parseconfig() values = [] class A: @@ -90,7 +94,7 @@ def pytest_configure(self): config.pluginmanager.register(A()) assert len(values) == 2 - def test_hook_tracing(self, _config_for_test) -> None: + def test_hook_tracing(self, _config_for_test: Config) -> None: pytestpm = _config_for_test.pluginmanager # fully initialized with plugins saveindent = [] @@ -139,9 +143,9 @@ def test_hook_proxy(self, pytester: Pytester) -> None: ihook_b = session.gethookproxy(pytester.path / "tests") assert ihook_a is not ihook_b - def test_hook_with_addoption(self, testdir): + def test_hook_with_addoption(self, pytester: Pytester) -> None: """Test that hooks can be used in a call to pytest_addoption""" - testdir.makepyfile( + pytester.makepyfile( newhooks=""" import pytest @pytest.hookspec(firstresult=True) @@ -149,7 +153,7 @@ def pytest_default_value(): pass """ ) - testdir.makepyfile( + pytester.makepyfile( myplugin=""" import newhooks def pytest_addhooks(pluginmanager): @@ -159,30 +163,32 @@ def pytest_addoption(parser, pluginmanager): parser.addoption("--config", help="Config, defaults to %(default)s", default=default_value) """ ) - testdir.makeconftest( + pytester.makeconftest( """ pytest_plugins=("myplugin",) def pytest_default_value(): return "default_value" """ ) - res = testdir.runpytest("--help") + res = pytester.runpytest("--help") res.stdout.fnmatch_lines(["*--config=CONFIG*default_value*"]) -def test_default_markers(testdir): - result = testdir.runpytest("--markers") +def test_default_markers(pytester: Pytester) -> None: + result = pytester.runpytest("--markers") result.stdout.fnmatch_lines(["*tryfirst*first*", "*trylast*last*"]) -def test_importplugin_error_message(testdir, pytestpm): +def test_importplugin_error_message( + pytester: Pytester, pytestpm: PytestPluginManager +) -> None: """Don't hide import errors when importing plugins and provide an easy to debug message. See #375 and #1998. """ - testdir.syspathinsert(testdir.tmpdir) - testdir.makepyfile( + pytester.syspathinsert(pytester.path) + pytester.makepyfile( qwe="""\ def test_traceback(): raise ImportError('Not possible to import: ☺') @@ -199,7 +205,7 @@ def test_traceback(): class TestPytestPluginManager: - def test_register_imported_modules(self): + def test_register_imported_modules(self) -> None: pm = PytestPluginManager() mod = types.ModuleType("x.y.pytest_hello") pm.register(mod) @@ -219,23 +225,27 @@ def test_canonical_import(self, monkeypatch): assert pm.get_plugin("pytest_xyz") == mod assert pm.is_registered(mod) - def test_consider_module(self, testdir, pytestpm: PytestPluginManager) -> None: - testdir.syspathinsert() - testdir.makepyfile(pytest_p1="#") - testdir.makepyfile(pytest_p2="#") + def test_consider_module( + self, pytester: Pytester, pytestpm: PytestPluginManager + ) -> None: + pytester.syspathinsert() + pytester.makepyfile(pytest_p1="#") + pytester.makepyfile(pytest_p2="#") mod = types.ModuleType("temp") mod.__dict__["pytest_plugins"] = ["pytest_p1", "pytest_p2"] pytestpm.consider_module(mod) assert pytestpm.get_plugin("pytest_p1").__name__ == "pytest_p1" assert pytestpm.get_plugin("pytest_p2").__name__ == "pytest_p2" - def test_consider_module_import_module(self, testdir, _config_for_test) -> None: + def test_consider_module_import_module( + self, pytester: Pytester, _config_for_test: Config + ) -> None: pytestpm = _config_for_test.pluginmanager mod = types.ModuleType("x") mod.__dict__["pytest_plugins"] = "pytest_a" - aplugin = testdir.makepyfile(pytest_a="#") - reprec = testdir.make_hook_recorder(pytestpm) - testdir.syspathinsert(aplugin.dirpath()) + aplugin = pytester.makepyfile(pytest_a="#") + reprec = pytester.make_hook_recorder(pytestpm) + pytester.syspathinsert(aplugin.parent) pytestpm.consider_module(mod) call = reprec.getcall(pytestpm.hook.pytest_plugin_registered.name) assert call.plugin.__name__ == "pytest_a" @@ -245,30 +255,37 @@ def test_consider_module_import_module(self, testdir, _config_for_test) -> None: values = reprec.getcalls("pytest_plugin_registered") assert len(values) == 1 - def test_consider_env_fails_to_import(self, monkeypatch, pytestpm): + def test_consider_env_fails_to_import( + self, monkeypatch: MonkeyPatch, pytestpm: PytestPluginManager + ) -> None: monkeypatch.setenv("PYTEST_PLUGINS", "nonexisting", prepend=",") with pytest.raises(ImportError): pytestpm.consider_env() @pytest.mark.filterwarnings("always") - def test_plugin_skip(self, testdir, monkeypatch): - p = testdir.makepyfile( + def test_plugin_skip(self, pytester: Pytester, monkeypatch: MonkeyPatch) -> None: + p = pytester.makepyfile( skipping1=""" import pytest pytest.skip("hello", allow_module_level=True) """ ) - p.copy(p.dirpath("skipping2.py")) + shutil.copy(p, p.with_name("skipping2.py")) monkeypatch.setenv("PYTEST_PLUGINS", "skipping2") - result = testdir.runpytest("-p", "skipping1", syspathinsert=True) + result = pytester.runpytest("-p", "skipping1", syspathinsert=True) assert result.ret == ExitCode.NO_TESTS_COLLECTED result.stdout.fnmatch_lines( ["*skipped plugin*skipping1*hello*", "*skipped plugin*skipping2*hello*"] ) - def test_consider_env_plugin_instantiation(self, testdir, monkeypatch, pytestpm): - testdir.syspathinsert() - testdir.makepyfile(xy123="#") + def test_consider_env_plugin_instantiation( + self, + pytester: Pytester, + monkeypatch: MonkeyPatch, + pytestpm: PytestPluginManager, + ) -> None: + pytester.syspathinsert() + pytester.makepyfile(xy123="#") monkeypatch.setitem(os.environ, "PYTEST_PLUGINS", "xy123") l1 = len(pytestpm.get_plugins()) pytestpm.consider_env() @@ -279,9 +296,11 @@ def test_consider_env_plugin_instantiation(self, testdir, monkeypatch, pytestpm) l3 = len(pytestpm.get_plugins()) assert l2 == l3 - def test_pluginmanager_ENV_startup(self, testdir, monkeypatch): - testdir.makepyfile(pytest_x500="#") - p = testdir.makepyfile( + def test_pluginmanager_ENV_startup( + self, pytester: Pytester, monkeypatch: MonkeyPatch + ) -> None: + pytester.makepyfile(pytest_x500="#") + p = pytester.makepyfile( """ import pytest def test_hello(pytestconfig): @@ -290,17 +309,19 @@ def test_hello(pytestconfig): """ ) monkeypatch.setenv("PYTEST_PLUGINS", "pytest_x500", prepend=",") - result = testdir.runpytest(p, syspathinsert=True) + result = pytester.runpytest(p, syspathinsert=True) assert result.ret == 0 result.stdout.fnmatch_lines(["*1 passed*"]) - def test_import_plugin_importname(self, testdir, pytestpm): + def test_import_plugin_importname( + self, pytester: Pytester, pytestpm: PytestPluginManager + ) -> None: pytest.raises(ImportError, pytestpm.import_plugin, "qweqwex.y") pytest.raises(ImportError, pytestpm.import_plugin, "pytest_qweqwx.y") - testdir.syspathinsert() + pytester.syspathinsert() pluginname = "pytest_hello" - testdir.makepyfile(**{pluginname: ""}) + pytester.makepyfile(**{pluginname: ""}) pytestpm.import_plugin("pytest_hello") len1 = len(pytestpm.get_plugins()) pytestpm.import_plugin("pytest_hello") @@ -311,25 +332,29 @@ def test_import_plugin_importname(self, testdir, pytestpm): plugin2 = pytestpm.get_plugin("pytest_hello") assert plugin2 is plugin1 - def test_import_plugin_dotted_name(self, testdir, pytestpm): + def test_import_plugin_dotted_name( + self, pytester: Pytester, pytestpm: PytestPluginManager + ) -> None: pytest.raises(ImportError, pytestpm.import_plugin, "qweqwex.y") pytest.raises(ImportError, pytestpm.import_plugin, "pytest_qweqwex.y") - testdir.syspathinsert() - testdir.mkpydir("pkg").join("plug.py").write("x=3") + pytester.syspathinsert() + pytester.mkpydir("pkg").joinpath("plug.py").write_text("x=3") pluginname = "pkg.plug" pytestpm.import_plugin(pluginname) mod = pytestpm.get_plugin("pkg.plug") assert mod.x == 3 - def test_consider_conftest_deps(self, testdir, pytestpm): - mod = testdir.makepyfile("pytest_plugins='xyz'").pyimport() + def test_consider_conftest_deps( + self, pytester: Pytester, pytestpm: PytestPluginManager, + ) -> None: + mod = import_path(pytester.makepyfile("pytest_plugins='xyz'")) with pytest.raises(ImportError): pytestpm.consider_conftest(mod) class TestPytestPluginManagerBootstrapming: - def test_preparse_args(self, pytestpm): + def test_preparse_args(self, pytestpm: PytestPluginManager) -> None: pytest.raises( ImportError, lambda: pytestpm.consider_preparse(["xyz", "-p", "hello123"]) ) @@ -346,7 +371,7 @@ def test_preparse_args(self, pytestpm): with pytest.raises(UsageError, match="^plugin main cannot be disabled$"): pytestpm.consider_preparse(["-p", "no:main"]) - def test_plugin_prevent_register(self, pytestpm): + def test_plugin_prevent_register(self, pytestpm: PytestPluginManager) -> None: pytestpm.consider_preparse(["xyz", "-p", "no:abc"]) l1 = pytestpm.get_plugins() pytestpm.register(42, name="abc") @@ -354,7 +379,9 @@ def test_plugin_prevent_register(self, pytestpm): assert len(l2) == len(l1) assert 42 not in l2 - def test_plugin_prevent_register_unregistered_alredy_registered(self, pytestpm): + def test_plugin_prevent_register_unregistered_alredy_registered( + self, pytestpm: PytestPluginManager + ) -> None: pytestpm.register(42, name="abc") l1 = pytestpm.get_plugins() assert 42 in l1 @@ -363,8 +390,8 @@ def test_plugin_prevent_register_unregistered_alredy_registered(self, pytestpm): assert 42 not in l2 def test_plugin_prevent_register_stepwise_on_cacheprovider_unregister( - self, pytestpm - ): + self, pytestpm: PytestPluginManager + ) -> None: """From PR #4304: The only way to unregister a module is documented at the end of https://docs.pytest.org/en/stable/plugins.html. @@ -380,7 +407,7 @@ def test_plugin_prevent_register_stepwise_on_cacheprovider_unregister( assert 42 not in l2 assert 43 not in l2 - def test_blocked_plugin_can_be_used(self, pytestpm): + def test_blocked_plugin_can_be_used(self, pytestpm: PytestPluginManager) -> None: pytestpm.consider_preparse(["xyz", "-p", "no:abc", "-p", "abc"]) assert pytestpm.has_plugin("abc") diff --git a/testing/test_reports.py b/testing/test_reports.py index b97b1fc2970..b376f6198ae 100644 --- a/testing/test_reports.py +++ b/testing/test_reports.py @@ -1,29 +1,30 @@ -from pathlib import Path from typing import Sequence from typing import Union +import py.path + import pytest from _pytest._code.code import ExceptionChainRepr from _pytest._code.code import ExceptionRepr from _pytest.config import Config -from _pytest.pytester import Testdir +from _pytest.pytester import Pytester from _pytest.reports import CollectReport from _pytest.reports import TestReport class TestReportSerialization: - def test_xdist_longrepr_to_str_issue_241(self, testdir: Testdir) -> None: + def test_xdist_longrepr_to_str_issue_241(self, pytester: Pytester) -> None: """Regarding issue pytest-xdist#241. This test came originally from test_remote.py in xdist (ca03269). """ - testdir.makepyfile( + pytester.makepyfile( """ def test_a(): assert False def test_b(): pass """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reports = reprec.getreports("pytest_runtest_logreport") assert len(reports) == 6 test_a_call = reports[1] @@ -35,12 +36,12 @@ def test_b(): pass assert test_b_call.outcome == "passed" assert test_b_call._to_json()["longrepr"] is None - def test_xdist_report_longrepr_reprcrash_130(self, testdir: Testdir) -> None: + def test_xdist_report_longrepr_reprcrash_130(self, pytester: Pytester) -> None: """Regarding issue pytest-xdist#130 This test came originally from test_remote.py in xdist (ca03269). """ - reprec = testdir.inline_runsource( + reprec = pytester.inline_runsource( """ def test_fail(): assert False, 'Expected Message' @@ -74,14 +75,14 @@ def test_fail(): # Missing section attribute PR171 assert added_section in a.longrepr.sections - def test_reprentries_serialization_170(self, testdir: Testdir) -> None: + def test_reprentries_serialization_170(self, pytester: Pytester) -> None: """Regarding issue pytest-xdist#170 This test came originally from test_remote.py in xdist (ca03269). """ from _pytest._code.code import ReprEntry - reprec = testdir.inline_runsource( + reprec = pytester.inline_runsource( """ def test_repr_entry(): x = 0 @@ -120,14 +121,14 @@ def test_repr_entry(): assert rep_entry.reprlocals.lines == a_entry.reprlocals.lines assert rep_entry.style == a_entry.style - def test_reprentries_serialization_196(self, testdir: Testdir) -> None: + def test_reprentries_serialization_196(self, pytester: Pytester) -> None: """Regarding issue pytest-xdist#196 This test came originally from test_remote.py in xdist (ca03269). """ from _pytest._code.code import ReprEntryNative - reprec = testdir.inline_runsource( + reprec = pytester.inline_runsource( """ def test_repr_entry_native(): x = 0 @@ -149,9 +150,9 @@ def test_repr_entry_native(): assert isinstance(rep_entries[i], ReprEntryNative) assert rep_entries[i].lines == a_entries[i].lines - def test_itemreport_outcomes(self, testdir: Testdir) -> None: + def test_itemreport_outcomes(self, pytester: Pytester) -> None: # This test came originally from test_remote.py in xdist (ca03269). - reprec = testdir.inline_runsource( + reprec = pytester.inline_runsource( """ import pytest def test_pass(): pass @@ -183,9 +184,9 @@ def test_xfail_imperative(): if rep.failed: assert newrep.longreprtext == rep.longreprtext - def test_collectreport_passed(self, testdir: Testdir) -> None: + def test_collectreport_passed(self, pytester: Pytester) -> None: """This test came originally from test_remote.py in xdist (ca03269).""" - reprec = testdir.inline_runsource("def test_func(): pass") + reprec = pytester.inline_runsource("def test_func(): pass") reports = reprec.getreports("pytest_collectreport") for rep in reports: d = rep._to_json() @@ -194,9 +195,9 @@ def test_collectreport_passed(self, testdir: Testdir) -> None: assert newrep.failed == rep.failed assert newrep.skipped == rep.skipped - def test_collectreport_fail(self, testdir: Testdir) -> None: + def test_collectreport_fail(self, pytester: Pytester) -> None: """This test came originally from test_remote.py in xdist (ca03269).""" - reprec = testdir.inline_runsource("qwe abc") + reprec = pytester.inline_runsource("qwe abc") reports = reprec.getreports("pytest_collectreport") assert reports for rep in reports: @@ -208,9 +209,9 @@ def test_collectreport_fail(self, testdir: Testdir) -> None: if rep.failed: assert newrep.longrepr == str(rep.longrepr) - def test_extended_report_deserialization(self, testdir: Testdir) -> None: + def test_extended_report_deserialization(self, pytester: Pytester) -> None: """This test came originally from test_remote.py in xdist (ca03269).""" - reprec = testdir.inline_runsource("qwe abc") + reprec = pytester.inline_runsource("qwe abc") reports = reprec.getreports("pytest_collectreport") assert reports for rep in reports: @@ -224,33 +225,33 @@ def test_extended_report_deserialization(self, testdir: Testdir) -> None: if rep.failed: assert newrep.longrepr == str(rep.longrepr) - def test_paths_support(self, testdir: Testdir) -> None: + def test_paths_support(self, pytester: Pytester) -> None: """Report attributes which are py.path or pathlib objects should become strings.""" - testdir.makepyfile( + pytester.makepyfile( """ def test_a(): assert False """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reports = reprec.getreports("pytest_runtest_logreport") assert len(reports) == 3 test_a_call = reports[1] - test_a_call.path1 = testdir.tmpdir # type: ignore[attr-defined] - test_a_call.path2 = Path(testdir.tmpdir) # type: ignore[attr-defined] + test_a_call.path1 = py.path.local(pytester.path) # type: ignore[attr-defined] + test_a_call.path2 = pytester.path # type: ignore[attr-defined] data = test_a_call._to_json() - assert data["path1"] == str(testdir.tmpdir) - assert data["path2"] == str(testdir.tmpdir) + assert data["path1"] == str(pytester.path) + assert data["path2"] == str(pytester.path) - def test_deserialization_failure(self, testdir: Testdir) -> None: + def test_deserialization_failure(self, pytester: Pytester) -> None: """Check handling of failure during deserialization of report types.""" - testdir.makepyfile( + pytester.makepyfile( """ def test_a(): assert False """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reports = reprec.getreports("pytest_runtest_logreport") assert len(reports) == 3 test_a_call = reports[1] @@ -265,9 +266,11 @@ def test_a(): TestReport._from_json(data) @pytest.mark.parametrize("report_class", [TestReport, CollectReport]) - def test_chained_exceptions(self, testdir: Testdir, tw_mock, report_class) -> None: + def test_chained_exceptions( + self, pytester: Pytester, tw_mock, report_class + ) -> None: """Check serialization/deserialization of report objects containing chained exceptions (#5786)""" - testdir.makepyfile( + pytester.makepyfile( """ def foo(): raise ValueError('value error') @@ -283,7 +286,7 @@ def test_a(): ) ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() if report_class is TestReport: reports: Union[ Sequence[TestReport], Sequence[CollectReport] @@ -338,14 +341,14 @@ def check_longrepr(longrepr: ExceptionChainRepr) -> None: # elsewhere and we do check the contents of the longrepr object after loading it. loaded_report.longrepr.toterminal(tw_mock) - def test_chained_exceptions_no_reprcrash(self, testdir: Testdir, tw_mock) -> None: + def test_chained_exceptions_no_reprcrash(self, pytester: Pytester, tw_mock) -> None: """Regression test for tracebacks without a reprcrash (#5971) This happens notably on exceptions raised by multiprocess.pool: the exception transfer from subprocess to main process creates an artificial exception, which ExceptionInfo can't obtain the ReprFileLocation from. """ - testdir.makepyfile( + pytester.makepyfile( """ from concurrent.futures import ProcessPoolExecutor @@ -358,8 +361,8 @@ def test_a(): """ ) - testdir.syspathinsert() - reprec = testdir.inline_run() + pytester.syspathinsert() + reprec = pytester.inline_run() reports = reprec.getreports("pytest_runtest_logreport") @@ -396,12 +399,13 @@ def check_longrepr(longrepr: object) -> None: loaded_report.longrepr.toterminal(tw_mock) def test_report_prevent_ConftestImportFailure_hiding_exception( - self, testdir: Testdir + self, pytester: Pytester ) -> None: - sub_dir = testdir.tmpdir.join("ns").ensure_dir() - sub_dir.join("conftest").new(ext=".py").write("import unknown") + sub_dir = pytester.path.joinpath("ns") + sub_dir.mkdir() + sub_dir.joinpath("conftest.py").write_text("import unknown") - result = testdir.runpytest_subprocess(".") + result = pytester.runpytest_subprocess(".") result.stdout.fnmatch_lines(["E *Error: No module named 'unknown'"]) result.stdout.no_fnmatch_line("ERROR - *ConftestImportFailure*") @@ -409,14 +413,14 @@ def test_report_prevent_ConftestImportFailure_hiding_exception( class TestHooks: """Test that the hooks are working correctly for plugins""" - def test_test_report(self, testdir: Testdir, pytestconfig: Config) -> None: - testdir.makepyfile( + def test_test_report(self, pytester: Pytester, pytestconfig: Config) -> None: + pytester.makepyfile( """ def test_a(): assert False def test_b(): pass """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reports = reprec.getreports("pytest_runtest_logreport") assert len(reports) == 6 for rep in reports: @@ -431,14 +435,14 @@ def test_b(): pass assert new_rep.when == rep.when assert new_rep.outcome == rep.outcome - def test_collect_report(self, testdir: Testdir, pytestconfig: Config) -> None: - testdir.makepyfile( + def test_collect_report(self, pytester: Pytester, pytestconfig: Config) -> None: + pytester.makepyfile( """ def test_a(): assert False def test_b(): pass """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reports = reprec.getreports("pytest_collectreport") assert len(reports) == 2 for rep in reports: @@ -457,14 +461,14 @@ def test_b(): pass "hook_name", ["pytest_runtest_logreport", "pytest_collectreport"] ) def test_invalid_report_types( - self, testdir: Testdir, pytestconfig: Config, hook_name: str + self, pytester: Pytester, pytestconfig: Config, hook_name: str ) -> None: - testdir.makepyfile( + pytester.makepyfile( """ def test_a(): pass """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reports = reprec.getreports(hook_name) assert reports rep = reports[0] diff --git a/testing/test_runner.py b/testing/test_runner.py index a1f1db48d06..8ce0f67354f 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -2,26 +2,28 @@ import os import sys import types +from pathlib import Path from typing import Dict from typing import List from typing import Tuple from typing import Type -import py - -import _pytest._code import pytest from _pytest import outcomes from _pytest import reports from _pytest import runner +from _pytest._code import ExceptionInfo +from _pytest._code.code import ExceptionChainRepr from _pytest.config import ExitCode +from _pytest.monkeypatch import MonkeyPatch from _pytest.outcomes import OutcomeException +from _pytest.pytester import Pytester class TestSetupState: - def test_setup(self, testdir) -> None: + def test_setup(self, pytester: Pytester) -> None: ss = runner.SetupState() - item = testdir.getitem("def test_func(): pass") + item = pytester.getitem("def test_func(): pass") values = [1] ss.prepare(item) ss.addfinalizer(values.pop, colitem=item) @@ -29,15 +31,15 @@ def test_setup(self, testdir) -> None: ss._pop_and_teardown() assert not values - def test_teardown_exact_stack_empty(self, testdir) -> None: - item = testdir.getitem("def test_func(): pass") + def test_teardown_exact_stack_empty(self, pytester: Pytester) -> None: + item = pytester.getitem("def test_func(): pass") ss = runner.SetupState() ss.teardown_exact(item, None) ss.teardown_exact(item, None) ss.teardown_exact(item, None) - def test_setup_fails_and_failure_is_cached(self, testdir) -> None: - item = testdir.getitem( + def test_setup_fails_and_failure_is_cached(self, pytester: Pytester) -> None: + item = pytester.getitem( """ def setup_module(mod): raise ValueError(42) @@ -48,7 +50,7 @@ def test_func(): pass pytest.raises(ValueError, lambda: ss.prepare(item)) pytest.raises(ValueError, lambda: ss.prepare(item)) - def test_teardown_multiple_one_fails(self, testdir) -> None: + def test_teardown_multiple_one_fails(self, pytester: Pytester) -> None: r = [] def fin1(): @@ -60,7 +62,7 @@ def fin2(): def fin3(): r.append("fin3") - item = testdir.getitem("def test_func(): pass") + item = pytester.getitem("def test_func(): pass") ss = runner.SetupState() ss.addfinalizer(fin1, item) ss.addfinalizer(fin2, item) @@ -70,7 +72,7 @@ def fin3(): assert err.value.args == ("oops",) assert r == ["fin3", "fin1"] - def test_teardown_multiple_fail(self, testdir) -> None: + def test_teardown_multiple_fail(self, pytester: Pytester) -> None: # Ensure the first exception is the one which is re-raised. # Ideally both would be reported however. def fin1(): @@ -79,7 +81,7 @@ def fin1(): def fin2(): raise Exception("oops2") - item = testdir.getitem("def test_func(): pass") + item = pytester.getitem("def test_func(): pass") ss = runner.SetupState() ss.addfinalizer(fin1, item) ss.addfinalizer(fin2, item) @@ -87,7 +89,7 @@ def fin2(): ss._callfinalizers(item) assert err.value.args == ("oops2",) - def test_teardown_multiple_scopes_one_fails(self, testdir) -> None: + def test_teardown_multiple_scopes_one_fails(self, pytester: Pytester) -> None: module_teardown = [] def fin_func(): @@ -96,7 +98,7 @@ def fin_func(): def fin_module(): module_teardown.append("fin_module") - item = testdir.getitem("def test_func(): pass") + item = pytester.getitem("def test_func(): pass") ss = runner.SetupState() ss.addfinalizer(fin_module, item.listchain()[-2]) ss.addfinalizer(fin_func, item) @@ -107,8 +109,8 @@ def fin_module(): class BaseFunctionalTests: - def test_passfunction(self, testdir) -> None: - reports = testdir.runitem( + def test_passfunction(self, pytester: Pytester) -> None: + reports = pytester.runitem( """ def test_func(): pass @@ -120,8 +122,8 @@ def test_func(): assert rep.outcome == "passed" assert not rep.longrepr - def test_failfunction(self, testdir) -> None: - reports = testdir.runitem( + def test_failfunction(self, pytester: Pytester) -> None: + reports = pytester.runitem( """ def test_func(): assert 0 @@ -135,8 +137,8 @@ def test_func(): assert rep.outcome == "failed" # assert isinstance(rep.longrepr, ReprExceptionInfo) - def test_skipfunction(self, testdir) -> None: - reports = testdir.runitem( + def test_skipfunction(self, pytester: Pytester) -> None: + reports = pytester.runitem( """ import pytest def test_func(): @@ -155,8 +157,8 @@ def test_func(): # assert rep.skipped.location.path # assert not rep.skipped.failurerepr - def test_skip_in_setup_function(self, testdir) -> None: - reports = testdir.runitem( + def test_skip_in_setup_function(self, pytester: Pytester) -> None: + reports = pytester.runitem( """ import pytest def setup_function(func): @@ -176,8 +178,8 @@ def test_func(): assert len(reports) == 2 assert reports[1].passed # teardown - def test_failure_in_setup_function(self, testdir) -> None: - reports = testdir.runitem( + def test_failure_in_setup_function(self, pytester: Pytester) -> None: + reports = pytester.runitem( """ import pytest def setup_function(func): @@ -193,8 +195,8 @@ def test_func(): assert rep.when == "setup" assert len(reports) == 2 - def test_failure_in_teardown_function(self, testdir) -> None: - reports = testdir.runitem( + def test_failure_in_teardown_function(self, pytester: Pytester) -> None: + reports = pytester.runitem( """ import pytest def teardown_function(func): @@ -213,8 +215,8 @@ def test_func(): # assert rep.longrepr.reprcrash.lineno == 3 # assert rep.longrepr.reprtraceback.reprentries - def test_custom_failure_repr(self, testdir) -> None: - testdir.makepyfile( + def test_custom_failure_repr(self, pytester: Pytester) -> None: + pytester.makepyfile( conftest=""" import pytest class Function(pytest.Function): @@ -222,7 +224,7 @@ def repr_failure(self, excinfo): return "hello" """ ) - reports = testdir.runitem( + reports = pytester.runitem( """ import pytest def test_func(): @@ -238,8 +240,8 @@ def test_func(): # assert rep.failed.where.path.basename == "test_func.py" # assert rep.failed.failurerepr == "hello" - def test_teardown_final_returncode(self, testdir) -> None: - rec = testdir.inline_runsource( + def test_teardown_final_returncode(self, pytester: Pytester) -> None: + rec = pytester.inline_runsource( """ def test_func(): pass @@ -249,8 +251,8 @@ def teardown_function(func): ) assert rec.ret == 1 - def test_logstart_logfinish_hooks(self, testdir) -> None: - rec = testdir.inline_runsource( + def test_logstart_logfinish_hooks(self, pytester: Pytester) -> None: + rec = pytester.inline_runsource( """ import pytest def test_func(): @@ -266,8 +268,8 @@ def test_func(): assert rep.nodeid == "test_logstart_logfinish_hooks.py::test_func" assert rep.location == ("test_logstart_logfinish_hooks.py", 1, "test_func") - def test_exact_teardown_issue90(self, testdir) -> None: - rec = testdir.inline_runsource( + def test_exact_teardown_issue90(self, pytester: Pytester) -> None: + rec = pytester.inline_runsource( """ import pytest @@ -306,9 +308,9 @@ def teardown_function(func): assert reps[5].nodeid.endswith("test_func") assert reps[5].failed - def test_exact_teardown_issue1206(self, testdir) -> None: + def test_exact_teardown_issue1206(self, pytester: Pytester) -> None: """Issue shadowing error with wrong number of arguments on teardown_method.""" - rec = testdir.inline_runsource( + rec = pytester.inline_runsource( """ import pytest @@ -335,14 +337,19 @@ def test_method(self): assert reps[2].nodeid.endswith("test_method") assert reps[2].failed assert reps[2].when == "teardown" - assert reps[2].longrepr.reprcrash.message in ( + longrepr = reps[2].longrepr + assert isinstance(longrepr, ExceptionChainRepr) + assert longrepr.reprcrash + assert longrepr.reprcrash.message in ( "TypeError: teardown_method() missing 2 required positional arguments: 'y' and 'z'", # Python >= 3.10 "TypeError: TestClass.teardown_method() missing 2 required positional arguments: 'y' and 'z'", ) - def test_failure_in_setup_function_ignores_custom_repr(self, testdir) -> None: - testdir.makepyfile( + def test_failure_in_setup_function_ignores_custom_repr( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( conftest=""" import pytest class Function(pytest.Function): @@ -350,7 +357,7 @@ def repr_failure(self, excinfo): assert 0 """ ) - reports = testdir.runitem( + reports = pytester.runitem( """ def setup_function(func): raise ValueError(42) @@ -369,9 +376,9 @@ def test_func(): # assert rep.outcome.where.path.basename == "test_func.py" # assert instanace(rep.failed.failurerepr, PythonFailureRepr) - def test_systemexit_does_not_bail_out(self, testdir) -> None: + def test_systemexit_does_not_bail_out(self, pytester: Pytester) -> None: try: - reports = testdir.runitem( + reports = pytester.runitem( """ def test_func(): raise SystemExit(42) @@ -383,9 +390,9 @@ def test_func(): assert rep.failed assert rep.when == "call" - def test_exit_propagates(self, testdir) -> None: + def test_exit_propagates(self, pytester: Pytester) -> None: try: - testdir.runitem( + pytester.runitem( """ import pytest def test_func(): @@ -405,9 +412,9 @@ def f(item): return f - def test_keyboardinterrupt_propagates(self, testdir) -> None: + def test_keyboardinterrupt_propagates(self, pytester: Pytester) -> None: try: - testdir.runitem( + pytester.runitem( """ def test_func(): raise KeyboardInterrupt("fake") @@ -420,8 +427,8 @@ def test_func(): class TestSessionReports: - def test_collect_result(self, testdir) -> None: - col = testdir.getmodulecol( + def test_collect_result(self, pytester: Pytester) -> None: + col = pytester.getmodulecol( """ def test_func1(): pass @@ -489,8 +496,8 @@ def raise_assertion(): @pytest.mark.xfail -def test_runtest_in_module_ordering(testdir) -> None: - p1 = testdir.makepyfile( +def test_runtest_in_module_ordering(pytester: Pytester) -> None: + p1 = pytester.makepyfile( """ import pytest def pytest_runtest_setup(item): # runs after class-level! @@ -517,7 +524,7 @@ def pytest_runtest_teardown(item): del item.function.mylist """ ) - result = testdir.runpytest(p1) + result = pytester.runpytest(p1) result.stdout.fnmatch_lines(["*2 passed*"]) @@ -547,8 +554,8 @@ def test_pytest_fail() -> None: assert s.startswith("Failed") -def test_pytest_exit_msg(testdir) -> None: - testdir.makeconftest( +def test_pytest_exit_msg(pytester: Pytester) -> None: + pytester.makeconftest( """ import pytest @@ -556,7 +563,7 @@ def pytest_configure(config): pytest.exit('oh noes') """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stderr.fnmatch_lines(["Exit: oh noes"]) @@ -570,22 +577,22 @@ def _strip_resource_warnings(lines): ] -def test_pytest_exit_returncode(testdir) -> None: - testdir.makepyfile( +def test_pytest_exit_returncode(pytester: Pytester) -> None: + pytester.makepyfile( """\ import pytest def test_foo(): pytest.exit("some exit msg", 99) """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*! *Exit: some exit msg !*"]) assert _strip_resource_warnings(result.stderr.lines) == [] assert result.ret == 99 # It prints to stderr also in case of exit during pytest_sessionstart. - testdir.makeconftest( + pytester.makeconftest( """\ import pytest @@ -593,7 +600,7 @@ def pytest_sessionstart(): pytest.exit("during_sessionstart", 98) """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*! *Exit: during_sessionstart !*"]) assert _strip_resource_warnings(result.stderr.lines) == [ "Exit: during_sessionstart" @@ -601,9 +608,9 @@ def pytest_sessionstart(): assert result.ret == 98 -def test_pytest_fail_notrace_runtest(testdir) -> None: +def test_pytest_fail_notrace_runtest(pytester: Pytester) -> None: """Test pytest.fail(..., pytrace=False) does not show tracebacks during test run.""" - testdir.makepyfile( + pytester.makepyfile( """ import pytest def test_hello(): @@ -612,14 +619,14 @@ def teardown_function(function): pytest.fail("world", pytrace=False) """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["world", "hello"]) result.stdout.no_fnmatch_line("*def teardown_function*") -def test_pytest_fail_notrace_collection(testdir) -> None: +def test_pytest_fail_notrace_collection(pytester: Pytester) -> None: """Test pytest.fail(..., pytrace=False) does not show tracebacks during collection.""" - testdir.makepyfile( + pytester.makepyfile( """ import pytest def some_internal_function(): @@ -627,17 +634,17 @@ def some_internal_function(): some_internal_function() """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["hello"]) result.stdout.no_fnmatch_line("*def some_internal_function()*") -def test_pytest_fail_notrace_non_ascii(testdir) -> None: +def test_pytest_fail_notrace_non_ascii(pytester: Pytester) -> None: """Fix pytest.fail with pytrace=False with non-ascii characters (#1178). This tests with native and unicode strings containing non-ascii chars. """ - testdir.makepyfile( + pytester.makepyfile( """\ import pytest @@ -645,28 +652,28 @@ def test_hello(): pytest.fail('oh oh: ☺', pytrace=False) """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*test_hello*", "oh oh: ☺"]) result.stdout.no_fnmatch_line("*def test_hello*") -def test_pytest_no_tests_collected_exit_status(testdir) -> None: - result = testdir.runpytest() +def test_pytest_no_tests_collected_exit_status(pytester: Pytester) -> None: + result = pytester.runpytest() result.stdout.fnmatch_lines(["*collected 0 items*"]) assert result.ret == ExitCode.NO_TESTS_COLLECTED - testdir.makepyfile( + pytester.makepyfile( test_foo=""" def test_foo(): assert 1 """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*collected 1 item*"]) result.stdout.fnmatch_lines(["*1 passed*"]) assert result.ret == ExitCode.OK - result = testdir.runpytest("-k nonmatch") + result = pytester.runpytest("-k nonmatch") result.stdout.fnmatch_lines(["*collected 1 item*"]) result.stdout.fnmatch_lines(["*1 deselected*"]) assert result.ret == ExitCode.NO_TESTS_COLLECTED @@ -677,7 +684,7 @@ def test_exception_printing_skip() -> None: try: pytest.skip("hello") except pytest.skip.Exception: - excinfo = _pytest._code.ExceptionInfo.from_current() + excinfo = ExceptionInfo.from_current() s = excinfo.exconly(tryshort=True) assert s.startswith("Skipped") @@ -698,10 +705,10 @@ def f(): excrepr = excinfo.getrepr() assert excrepr is not None assert excrepr.reprcrash is not None - path = py.path.local(excrepr.reprcrash.path) + path = Path(excrepr.reprcrash.path) # check that importorskip reports the actual call # in this test the test_runner.py file - assert path.purebasename == "test_runner" + assert path.stem == "test_runner" pytest.raises(SyntaxError, pytest.importorskip, "x y z") pytest.raises(SyntaxError, pytest.importorskip, "x=y") mod = types.ModuleType("hello123") @@ -712,9 +719,7 @@ def f(): mod2 = pytest.importorskip("hello123", minversion="1.3") assert mod2 == mod except pytest.skip.Exception: # pragma: no cover - assert False, "spurious skip: {}".format( - _pytest._code.ExceptionInfo.from_current() - ) + assert False, f"spurious skip: {ExceptionInfo.from_current()}" def test_importorskip_imports_last_module_part() -> None: @@ -732,14 +737,12 @@ def test_importorskip_dev_module(monkeypatch) -> None: with pytest.raises(pytest.skip.Exception): pytest.importorskip("mockmodule1", minversion="0.14.0") except pytest.skip.Exception: # pragma: no cover - assert False, "spurious skip: {}".format( - _pytest._code.ExceptionInfo.from_current() - ) + assert False, f"spurious skip: {ExceptionInfo.from_current()}" -def test_importorskip_module_level(testdir) -> None: +def test_importorskip_module_level(pytester: Pytester) -> None: """`importorskip` must be able to skip entire modules when used at module level.""" - testdir.makepyfile( + pytester.makepyfile( """ import pytest foobarbaz = pytest.importorskip("foobarbaz") @@ -748,13 +751,13 @@ def test_foo(): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*collected 0 items / 1 skipped*"]) -def test_importorskip_custom_reason(testdir) -> None: +def test_importorskip_custom_reason(pytester: Pytester) -> None: """Make sure custom reasons are used.""" - testdir.makepyfile( + pytester.makepyfile( """ import pytest foobarbaz = pytest.importorskip("foobarbaz2", reason="just because") @@ -763,13 +766,13 @@ def test_foo(): pass """ ) - result = testdir.runpytest("-ra") + result = pytester.runpytest("-ra") result.stdout.fnmatch_lines(["*just because*"]) result.stdout.fnmatch_lines(["*collected 0 items / 1 skipped*"]) -def test_pytest_cmdline_main(testdir) -> None: - p = testdir.makepyfile( +def test_pytest_cmdline_main(pytester: Pytester) -> None: + p = pytester.makepyfile( """ import pytest def test_hello(): @@ -786,8 +789,8 @@ def test_hello(): assert ret == 0 -def test_unicode_in_longrepr(testdir) -> None: - testdir.makeconftest( +def test_unicode_in_longrepr(pytester: Pytester) -> None: + pytester.makeconftest( """\ import pytest @pytest.hookimpl(hookwrapper=True) @@ -798,19 +801,19 @@ def pytest_runtest_makereport(): rep.longrepr = 'ä' """ ) - testdir.makepyfile( + pytester.makepyfile( """ def test_out(): assert 0 """ ) - result = testdir.runpytest() + result = pytester.runpytest() assert result.ret == 1 assert "UnicodeEncodeError" not in result.stderr.str() -def test_failure_in_setup(testdir) -> None: - testdir.makepyfile( +def test_failure_in_setup(pytester: Pytester) -> None: + pytester.makepyfile( """ def setup_module(): 0/0 @@ -818,24 +821,26 @@ def test_func(): pass """ ) - result = testdir.runpytest("--tb=line") + result = pytester.runpytest("--tb=line") result.stdout.no_fnmatch_line("*def setup_module*") -def test_makereport_getsource(testdir) -> None: - testdir.makepyfile( +def test_makereport_getsource(pytester: Pytester) -> None: + pytester.makepyfile( """ def test_foo(): if False: pass else: assert False """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.no_fnmatch_line("*INTERNALERROR*") result.stdout.fnmatch_lines(["*else: assert False*"]) -def test_makereport_getsource_dynamic_code(testdir, monkeypatch) -> None: +def test_makereport_getsource_dynamic_code( + pytester: Pytester, monkeypatch: MonkeyPatch +) -> None: """Test that exception in dynamically generated code doesn't break getting the source line.""" import inspect @@ -849,7 +854,7 @@ def findsource(obj): monkeypatch.setattr(inspect, "findsource", findsource) - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -861,7 +866,7 @@ def test_fix(foo): assert False """ ) - result = testdir.runpytest("-vv") + result = pytester.runpytest("-vv") result.stdout.no_fnmatch_line("*INTERNALERROR*") result.stdout.fnmatch_lines(["*test_fix*", "*fixture*'missing'*not found*"]) @@ -896,12 +901,12 @@ def runtest(self): assert not hasattr(sys, "last_traceback") -def test_current_test_env_var(testdir, monkeypatch) -> None: +def test_current_test_env_var(pytester: Pytester, monkeypatch: MonkeyPatch) -> None: pytest_current_test_vars: List[Tuple[str, str]] = [] monkeypatch.setattr( sys, "pytest_current_test_vars", pytest_current_test_vars, raising=False ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest import sys @@ -917,7 +922,7 @@ def test(fix): sys.pytest_current_test_vars.append(('call', os.environ['PYTEST_CURRENT_TEST'])) """ ) - result = testdir.runpytest_inprocess() + result = pytester.runpytest_inprocess() assert result.ret == 0 test_id = "test_current_test_env_var.py::test" assert pytest_current_test_vars == [ @@ -934,8 +939,8 @@ class TestReportContents: def getrunner(self): return lambda item: runner.runtestprotocol(item, log=False) - def test_longreprtext_pass(self, testdir) -> None: - reports = testdir.runitem( + def test_longreprtext_pass(self, pytester: Pytester) -> None: + reports = pytester.runitem( """ def test_func(): pass @@ -944,9 +949,9 @@ def test_func(): rep = reports[1] assert rep.longreprtext == "" - def test_longreprtext_skip(self, testdir) -> None: + def test_longreprtext_skip(self, pytester: Pytester) -> None: """TestReport.longreprtext can handle non-str ``longrepr`` attributes (#7559)""" - reports = testdir.runitem( + reports = pytester.runitem( """ import pytest def test_func(): @@ -957,22 +962,22 @@ def test_func(): assert isinstance(call_rep.longrepr, tuple) assert "Skipped" in call_rep.longreprtext - def test_longreprtext_collect_skip(self, testdir) -> None: + def test_longreprtext_collect_skip(self, pytester: Pytester) -> None: """CollectReport.longreprtext can handle non-str ``longrepr`` attributes (#7559)""" - testdir.makepyfile( + pytester.makepyfile( """ import pytest pytest.skip(allow_module_level=True) """ ) - rec = testdir.inline_run() + rec = pytester.inline_run() calls = rec.getcalls("pytest_collectreport") _, call = calls assert isinstance(call.report.longrepr, tuple) assert "Skipped" in call.report.longreprtext - def test_longreprtext_failure(self, testdir) -> None: - reports = testdir.runitem( + def test_longreprtext_failure(self, pytester: Pytester) -> None: + reports = pytester.runitem( """ def test_func(): x = 1 @@ -982,8 +987,8 @@ def test_func(): rep = reports[1] assert "assert 1 == 4" in rep.longreprtext - def test_captured_text(self, testdir) -> None: - reports = testdir.runitem( + def test_captured_text(self, pytester: Pytester) -> None: + reports = pytester.runitem( """ import pytest import sys @@ -1012,8 +1017,8 @@ def test_func(fix): assert call.capstderr == "setup: stderr\ncall: stderr\n" assert teardown.capstderr == "setup: stderr\ncall: stderr\nteardown: stderr\n" - def test_no_captured_text(self, testdir) -> None: - reports = testdir.runitem( + def test_no_captured_text(self, pytester: Pytester) -> None: + reports = pytester.runitem( """ def test_func(): pass @@ -1023,8 +1028,8 @@ def test_func(): assert rep.capstdout == "" assert rep.capstderr == "" - def test_longrepr_type(self, testdir) -> None: - reports = testdir.runitem( + def test_longrepr_type(self, pytester: Pytester) -> None: + reports = pytester.runitem( """ import pytest def test_func(): @@ -1032,7 +1037,7 @@ def test_func(): """ ) rep = reports[1] - assert isinstance(rep.longrepr, _pytest._code.code.ExceptionRepr) + assert isinstance(rep.longrepr, ExceptionChainRepr) def test_outcome_exception_bad_msg() -> None: From 9ccbf5b89906604dc76daee1fc07c1d26b6b5128 Mon Sep 17 00:00:00 2001 From: Jakob van Santen Date: Tue, 15 Dec 2020 12:49:29 +0100 Subject: [PATCH 0011/2772] python_api: handle array-like args in approx() (#8137) --- changelog/8132.bugfix.rst | 10 ++++++++++ src/_pytest/python_api.py | 33 +++++++++++++++++++++++++++------ testing/python/approx.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 6 deletions(-) create mode 100644 changelog/8132.bugfix.rst diff --git a/changelog/8132.bugfix.rst b/changelog/8132.bugfix.rst new file mode 100644 index 00000000000..5be5e567491 --- /dev/null +++ b/changelog/8132.bugfix.rst @@ -0,0 +1,10 @@ +Fixed regression in ``approx``: in 6.2.0 ``approx`` no longer raises +``TypeError`` when dealing with non-numeric types, falling back to normal comparison. +Before 6.2.0, array types like tf.DeviceArray fell through to the scalar case, +and happened to compare correctly to a scalar if they had only one element. +After 6.2.0, these types began failing, because they inherited neither from +standard Python number hierarchy nor from ``numpy.ndarray``. + +``approx`` now converts arguments to ``numpy.ndarray`` if they expose the array +protocol and are not scalars. This treats array-like objects like numpy arrays, +regardless of size. diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index bae2076892b..81ce4f89539 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -15,9 +15,14 @@ from typing import Pattern from typing import Tuple from typing import Type +from typing import TYPE_CHECKING from typing import TypeVar from typing import Union +if TYPE_CHECKING: + from numpy import ndarray + + import _pytest._code from _pytest.compat import final from _pytest.compat import STRING_TYPES @@ -232,10 +237,11 @@ def __repr__(self) -> str: def __eq__(self, actual) -> bool: """Return whether the given value is equal to the expected value within the pre-specified tolerance.""" - if _is_numpy_array(actual): + asarray = _as_numpy_array(actual) + if asarray is not None: # Call ``__eq__()`` manually to prevent infinite-recursion with # numpy<1.13. See #3748. - return all(self.__eq__(a) for a in actual.flat) + return all(self.__eq__(a) for a in asarray.flat) # Short-circuit exact equality. if actual == self.expected: @@ -521,6 +527,7 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: elif isinstance(expected, Mapping): cls = ApproxMapping elif _is_numpy_array(expected): + expected = _as_numpy_array(expected) cls = ApproxNumpy elif ( isinstance(expected, Iterable) @@ -536,16 +543,30 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: def _is_numpy_array(obj: object) -> bool: - """Return true if the given object is a numpy array. + """ + Return true if the given object is implicitly convertible to ndarray, + and numpy is already imported. + """ + return _as_numpy_array(obj) is not None + - A special effort is made to avoid importing numpy unless it's really necessary. +def _as_numpy_array(obj: object) -> Optional["ndarray"]: + """ + Return an ndarray if the given object is implicitly convertible to ndarray, + and numpy is already imported, otherwise None. """ import sys np: Any = sys.modules.get("numpy") if np is not None: - return isinstance(obj, np.ndarray) - return False + # avoid infinite recursion on numpy scalars, which have __array__ + if np.isscalar(obj): + return None + elif isinstance(obj, np.ndarray): + return obj + elif hasattr(obj, "__array__") or hasattr("obj", "__array_interface__"): + return np.asarray(obj) + return None # builtin pytest.raises helper diff --git a/testing/python/approx.py b/testing/python/approx.py index 91c1f3f85de..e76d6b774d6 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -447,6 +447,36 @@ def test_numpy_array_wrong_shape(self): assert a12 != approx(a21) assert a21 != approx(a12) + def test_numpy_array_protocol(self): + """ + array-like objects such as tensorflow's DeviceArray are handled like ndarray. + See issue #8132 + """ + np = pytest.importorskip("numpy") + + class DeviceArray: + def __init__(self, value, size): + self.value = value + self.size = size + + def __array__(self): + return self.value * np.ones(self.size) + + class DeviceScalar: + def __init__(self, value): + self.value = value + + def __array__(self): + return np.array(self.value) + + expected = 1 + actual = 1 + 1e-6 + assert approx(expected) == DeviceArray(actual, size=1) + assert approx(expected) == DeviceArray(actual, size=2) + assert approx(expected) == DeviceScalar(actual) + assert approx(DeviceScalar(expected)) == actual + assert approx(DeviceScalar(expected)) == DeviceScalar(actual) + def test_doctests(self, mocked_doctest_runner) -> None: import doctest From 56600414df31431f1e0c2d964d0ad6761f83dd5d Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 15 Dec 2020 12:39:06 -0300 Subject: [PATCH 0012/2772] Merge pull request #8149 from pytest-dev/release-6.2.1 Prepare release 6.2.1 (cherry picked from commit a566eb9c7085d7732127420bd7ce5ec1f7319fba) --- changelog/7678.bugfix.rst | 2 -- changelog/8132.bugfix.rst | 10 ---------- doc/en/announce/index.rst | 1 + doc/en/announce/release-6.2.1.rst | 20 ++++++++++++++++++++ doc/en/changelog.rst | 22 ++++++++++++++++++++++ doc/en/getting-started.rst | 2 +- 6 files changed, 44 insertions(+), 13 deletions(-) delete mode 100644 changelog/7678.bugfix.rst delete mode 100644 changelog/8132.bugfix.rst create mode 100644 doc/en/announce/release-6.2.1.rst diff --git a/changelog/7678.bugfix.rst b/changelog/7678.bugfix.rst deleted file mode 100644 index 4adc6ffd119..00000000000 --- a/changelog/7678.bugfix.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fixed bug where ``ImportPathMismatchError`` would be raised for files compiled in -the host and loaded later from an UNC mounted path (Windows). diff --git a/changelog/8132.bugfix.rst b/changelog/8132.bugfix.rst deleted file mode 100644 index 5be5e567491..00000000000 --- a/changelog/8132.bugfix.rst +++ /dev/null @@ -1,10 +0,0 @@ -Fixed regression in ``approx``: in 6.2.0 ``approx`` no longer raises -``TypeError`` when dealing with non-numeric types, falling back to normal comparison. -Before 6.2.0, array types like tf.DeviceArray fell through to the scalar case, -and happened to compare correctly to a scalar if they had only one element. -After 6.2.0, these types began failing, because they inherited neither from -standard Python number hierarchy nor from ``numpy.ndarray``. - -``approx`` now converts arguments to ``numpy.ndarray`` if they expose the array -protocol and are not scalars. This treats array-like objects like numpy arrays, -regardless of size. diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index 003a0a1a9ca..e7cac2a1c41 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,7 @@ Release announcements :maxdepth: 2 + release-6.2.1 release-6.2.0 release-6.1.2 release-6.1.1 diff --git a/doc/en/announce/release-6.2.1.rst b/doc/en/announce/release-6.2.1.rst new file mode 100644 index 00000000000..f9e71618351 --- /dev/null +++ b/doc/en/announce/release-6.2.1.rst @@ -0,0 +1,20 @@ +pytest-6.2.1 +======================================= + +pytest 6.2.1 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. + +Thanks to all of the contributors to this release: + +* Bruno Oliveira +* Jakob van Santen +* Ran Benita + + +Happy testing, +The pytest Development Team diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index 77340b1bb84..6d66ad1d8dc 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -28,6 +28,28 @@ with advance notice in the **Deprecations** section of releases. .. towncrier release notes start +pytest 6.2.1 (2020-12-15) +========================= + +Bug Fixes +--------- + +- `#7678 `_: Fixed bug where ``ImportPathMismatchError`` would be raised for files compiled in + the host and loaded later from an UNC mounted path (Windows). + + +- `#8132 `_: Fixed regression in ``approx``: in 6.2.0 ``approx`` no longer raises + ``TypeError`` when dealing with non-numeric types, falling back to normal comparison. + Before 6.2.0, array types like tf.DeviceArray fell through to the scalar case, + and happened to compare correctly to a scalar if they had only one element. + After 6.2.0, these types began failing, because they inherited neither from + standard Python number hierarchy nor from ``numpy.ndarray``. + + ``approx`` now converts arguments to ``numpy.ndarray`` if they expose the array + protocol and are not scalars. This treats array-like objects like numpy arrays, + regardless of size. + + pytest 6.2.0 (2020-12-12) ========================= diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index fe15c218cde..09410585dc7 100644 --- a/doc/en/getting-started.rst +++ b/doc/en/getting-started.rst @@ -28,7 +28,7 @@ Install ``pytest`` .. code-block:: bash $ pytest --version - pytest 6.2.0 + pytest 6.2.1 .. _`simpletest`: From d46ecbc18b74b895b71e257bf07836cd2cfae89e Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 15 Dec 2020 20:16:28 +0200 Subject: [PATCH 0013/2772] terminal: fix "()" skip reason in test status line --- changelog/8152.bugfix.rst | 1 + src/_pytest/outcomes.py | 2 +- src/_pytest/terminal.py | 2 ++ testing/test_terminal.py | 26 ++++++++++++++++++++++++++ 4 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 changelog/8152.bugfix.rst diff --git a/changelog/8152.bugfix.rst b/changelog/8152.bugfix.rst new file mode 100644 index 00000000000..d79a832de41 --- /dev/null +++ b/changelog/8152.bugfix.rst @@ -0,0 +1 @@ +Fixed "()" being shown as a skip reason in the verbose test summary line when the reason is empty. diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py index f0607cbd849..8f6203fd7fa 100644 --- a/src/_pytest/outcomes.py +++ b/src/_pytest/outcomes.py @@ -38,7 +38,7 @@ def __init__(self, msg: Optional[str] = None, pytrace: bool = True) -> None: self.pytrace = pytrace def __repr__(self) -> str: - if self.msg: + if self.msg is not None: return self.msg return f"<{self.__class__.__name__} instance>" diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index 39adfaaa310..f5d4e1f8ddc 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -1403,4 +1403,6 @@ def _get_raw_skip_reason(report: TestReport) -> str: _, _, reason = report.longrepr if reason.startswith("Skipped: "): reason = reason[len("Skipped: ") :] + elif reason == "Skipped": + reason = "" return reason diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 6319188a75e..6d0a23fe0f1 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -366,6 +366,26 @@ def test_3(): @pytest.mark.xfail(reason="") def test_4(): assert False + + @pytest.mark.skip + def test_5(): + pass + + @pytest.mark.xfail + def test_6(): + pass + + def test_7(): + pytest.skip() + + def test_8(): + pytest.skip("888 is great") + + def test_9(): + pytest.xfail() + + def test_10(): + pytest.xfail("It's 🕙 o'clock") """ ) result = pytester.runpytest("-v") @@ -375,6 +395,12 @@ def test_4(): "test_verbose_skip_reason.py::test_2 XPASS (456) *", "test_verbose_skip_reason.py::test_3 XFAIL (789) *", "test_verbose_skip_reason.py::test_4 XFAIL *", + "test_verbose_skip_reason.py::test_5 SKIPPED (unconditional skip) *", + "test_verbose_skip_reason.py::test_6 XPASS *", + "test_verbose_skip_reason.py::test_7 SKIPPED *", + "test_verbose_skip_reason.py::test_8 SKIPPED (888 is great) *", + "test_verbose_skip_reason.py::test_9 XFAIL *", + "test_verbose_skip_reason.py::test_10 XFAIL (It's 🕙 o'clock) *", ] ) From f1a1de22579c11fd1e4301e0c6648ae46a74a1d3 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 16 Dec 2020 07:50:02 -0300 Subject: [PATCH 0014/2772] Use manual trigger to prepare release PRs (#8150) Co-authored-by: Ran Benita --- .github/workflows/prepare-release-pr.yml | 42 +++++++ scripts/prepare-release-pr.py | 151 +++++++++++++++++++++++ tox.ini | 7 ++ 3 files changed, 200 insertions(+) create mode 100644 .github/workflows/prepare-release-pr.yml create mode 100644 scripts/prepare-release-pr.py diff --git a/.github/workflows/prepare-release-pr.yml b/.github/workflows/prepare-release-pr.yml new file mode 100644 index 00000000000..848dd78a413 --- /dev/null +++ b/.github/workflows/prepare-release-pr.yml @@ -0,0 +1,42 @@ +name: prepare release pr + +on: + workflow_dispatch: + inputs: + branch: + description: 'Branch to base the release from' + required: true + default: '' + major: + description: 'Major release? (yes/no)' + required: true + default: 'no' + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: "3.8" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install --upgrade setuptools tox + + - name: Prepare release PR (minor/patch release) + if: github.event.inputs.branch.major == 'no' + run: | + tox -e prepare-release-pr -- ${{ github.event.inputs.branch }} ${{ secrets.chatops }} + + - name: Prepare release PR (major release) + if: github.event.inputs.branch.major == 'yes' + run: | + tox -e prepare-release-pr -- ${{ github.event.inputs.branch }} ${{ secrets.chatops }} --major diff --git a/scripts/prepare-release-pr.py b/scripts/prepare-release-pr.py new file mode 100644 index 00000000000..538a5af5a41 --- /dev/null +++ b/scripts/prepare-release-pr.py @@ -0,0 +1,151 @@ +""" +This script is part of the pytest release process which is triggered manually in the Actions +tab of the repository. + +The user will need to enter the base branch to start the release from (for example +``6.1.x`` or ``master``) and if it should be a major release. + +The appropriate version will be obtained based on the given branch automatically. + +After that, it will create a release using the `release` tox environment, and push a new PR. + +**Secret**: currently the secret is defined in the @pytestbot account, +which the core maintainers have access to. There we created a new secret named `chatops` +with write access to the repository. +""" +import argparse +import re +from pathlib import Path +from subprocess import check_call +from subprocess import check_output +from subprocess import run + +from colorama import Fore +from colorama import init +from github3.repos import Repository + + +class InvalidFeatureRelease(Exception): + pass + + +SLUG = "pytest-dev/pytest" + +PR_BODY = """\ +Created automatically from manual trigger. + +Once all builds pass and it has been **approved** by one or more maintainers, the build +can be released by pushing a tag `{version}` to this repository. +""" + + +def login(token: str) -> Repository: + import github3 + + github = github3.login(token=token) + owner, repo = SLUG.split("/") + return github.repository(owner, repo) + + +def prepare_release_pr(base_branch: str, is_major: bool, token: str) -> None: + print() + print(f"Processing release for branch {Fore.CYAN}{base_branch}") + + check_call(["git", "checkout", f"origin/{base_branch}"]) + + try: + version = find_next_version(base_branch, is_major) + except InvalidFeatureRelease as e: + print(f"{Fore.RED}{e}") + raise SystemExit(1) + + print(f"Version: {Fore.CYAN}{version}") + + release_branch = f"release-{version}" + + run( + ["git", "config", "user.name", "pytest bot"], + text=True, + check=True, + capture_output=True, + ) + run( + ["git", "config", "user.email", "pytestbot@gmail.com"], + text=True, + check=True, + capture_output=True, + ) + + run( + ["git", "checkout", "-b", release_branch, f"origin/{base_branch}"], + text=True, + check=True, + capture_output=True, + ) + + print(f"Branch {Fore.CYAN}{release_branch}{Fore.RESET} created.") + + # important to use tox here because we have changed branches, so dependencies + # might have changed as well + cmdline = ["tox", "-e", "release", "--", version, "--skip-check-links"] + print("Running", " ".join(cmdline)) + run( + cmdline, text=True, check=True, capture_output=True, + ) + + oauth_url = f"https://{token}:x-oauth-basic@github.com/{SLUG}.git" + run( + ["git", "push", oauth_url, f"HEAD:{release_branch}", "--force"], + text=True, + check=True, + capture_output=True, + ) + print(f"Branch {Fore.CYAN}{release_branch}{Fore.RESET} pushed.") + + body = PR_BODY.format(version=version) + repo = login(token) + pr = repo.create_pull( + f"Prepare release {version}", base=base_branch, head=release_branch, body=body, + ) + print(f"Pull request {Fore.CYAN}{pr.url}{Fore.RESET} created.") + + +def find_next_version(base_branch: str, is_major: bool) -> str: + output = check_output(["git", "tag"], encoding="UTF-8") + valid_versions = [] + for v in output.splitlines(): + m = re.match(r"\d.\d.\d+$", v.strip()) + if m: + valid_versions.append(tuple(int(x) for x in v.split("."))) + + valid_versions.sort() + last_version = valid_versions[-1] + + changelog = Path("changelog") + + features = list(changelog.glob("*.feature.rst")) + breaking = list(changelog.glob("*.breaking.rst")) + is_feature_release = features or breaking + + if is_major: + return f"{last_version[0]+1}.0.0" + elif is_feature_release: + return f"{last_version[0]}.{last_version[1] + 1}.0" + else: + return f"{last_version[0]}.{last_version[1]}.{last_version[2] + 1}" + + +def main() -> None: + init(autoreset=True) + parser = argparse.ArgumentParser() + parser.add_argument("base_branch") + parser.add_argument("token") + parser.add_argument("--major", action="store_true", default=False) + options = parser.parse_args() + prepare_release_pr( + base_branch=options.base_branch, is_major=options.major, token=options.token + ) + + +if __name__ == "__main__": + main() diff --git a/tox.ini b/tox.ini index f0cfaa460fb..43e151c07aa 100644 --- a/tox.ini +++ b/tox.ini @@ -157,6 +157,13 @@ passenv = {[testenv:release]passenv} deps = {[testenv:release]deps} commands = python scripts/release-on-comment.py {posargs} +[testenv:prepare-release-pr] +decription = prepare a release PR from a manual trigger in GitHub actions +usedevelop = {[testenv:release]usedevelop} +passenv = {[testenv:release]passenv} +deps = {[testenv:release]deps} +commands = python scripts/prepare-release-pr.py {posargs} + [testenv:publish-gh-release-notes] description = create GitHub release after deployment basepython = python3 From a1c5111a404aada1444f28cf23bf52cbb00402d3 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 16 Dec 2020 07:53:05 -0300 Subject: [PATCH 0015/2772] Fix events variable in prepare-release-pr.yml --- .github/workflows/prepare-release-pr.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/prepare-release-pr.yml b/.github/workflows/prepare-release-pr.yml index 848dd78a413..dec35236430 100644 --- a/.github/workflows/prepare-release-pr.yml +++ b/.github/workflows/prepare-release-pr.yml @@ -32,11 +32,11 @@ jobs: pip install --upgrade setuptools tox - name: Prepare release PR (minor/patch release) - if: github.event.inputs.branch.major == 'no' + if: github.event.inputs.major == 'no' run: | tox -e prepare-release-pr -- ${{ github.event.inputs.branch }} ${{ secrets.chatops }} - name: Prepare release PR (major release) - if: github.event.inputs.branch.major == 'yes' + if: github.event.inputs.major == 'yes' run: | tox -e prepare-release-pr -- ${{ github.event.inputs.branch }} ${{ secrets.chatops }} --major From cab16f3aac4f81090fb376b3cb520804b4ebdd15 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 16 Dec 2020 11:19:45 -0300 Subject: [PATCH 0016/2772] Use transparent PNG for logo (#8159) --- README.rst | 3 ++- doc/en/conf.py | 2 +- doc/en/img/pytest1.png | Bin 6010 -> 40974 bytes doc/en/img/pytest_logo_curves.svg | 29 +++++++++++++++++++++++++++++ 4 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 doc/en/img/pytest_logo_curves.svg diff --git a/README.rst b/README.rst index 398d6451c58..0fb4e363b1c 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,7 @@ -.. image:: https://docs.pytest.org/en/stable/_static/pytest1.png +.. image:: https://github.com/pytest-dev/pytest/raw/master/doc/en/img/pytest_logo_curves.svg :target: https://docs.pytest.org/en/stable/ :align: center + :height: 200 :alt: pytest diff --git a/doc/en/conf.py b/doc/en/conf.py index 2f3a2baf44b..e34ae6856f0 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -159,7 +159,7 @@ # The name of an image file (relative to this directory) to place at the top # of the sidebar. -html_logo = "img/pytest1.png" +html_logo = "img/pytest_logo_curves.svg" # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 diff --git a/doc/en/img/pytest1.png b/doc/en/img/pytest1.png index e8064a694ca8eff0f2167d29ad13609f9a99a46e..498e70485d214c242ab75ce24f93225223fc5f8c 100644 GIT binary patch literal 40974 zcmeFYc|6o@^gpbkER`+EUfGHeLb8QYDf_-x3XweqV@Z;oHpw26o$O;7#-6grjAbw= z+b~Rw!I<$}qx<`N{(AoYJdMpL0d&>)txS$jwMYLv!NJ?Hh(P zG<0a{Fa1$)<*MAhN8o=)AKtxngJz%l-zRw9I~tnIwmUb}js3<~CUqjyAzYm^Pk!#I zyW9{ixXJP+I!m3U-LyTN#b@Sw_z?|-bDgqzj@Ryc$Va~l(=YtQw3OXYv#9Y@A)C#b zWAgK4!UryV zVgonW(R9Sq;HhKaLO!@L34Y->!Bf;gBldqT39$PAtv_3FV^4j_>5!=%Tsd>k!bU=T znSiXU?1a<=8m(MII?e-KD=^BNTI@av*lcvJsi`>`|24Rao}HLLc38=@S8{N0Fl(_r zLn1#c-=5mOCeUo$H1*0cUO-OxeF*NmN&N(SZBtV)0y@##)3eps>{oppT)T(% zOYCvH!6qwP`3>i50j?lk#$8=!3I#MTRWMmuSskXB4MOTdkl_ae zuVf=?+gZW8i~ODssPEFoVJI#SsPEP<)YjIzdptg%n5OLiN5p`7xj8N_E}`KHxO!hh zUO^!t>O7z*?w>%qm_(J&y>Ik~y{QjA6>jLyJ@|&qLqrlC^&5#R=l**p@{k?-jh>z! z(+YwANNF91S+PjV%35^?{~7{xo-=SsV`5%JCMS<+Z`u6!+Qagzl93;ilS>{=KYMb( zSL~VfbLt0QORf9MQ>Q4}omU+rkPTk7@ZH$ilmGj2PbAJU{9vV$#bspzf7jI>dtqIe zy4oIrdvt){mXmxc-1}u{%q;b72Ob`t%x4E;xdB9ayHu!&z9I&1NC|ufEzSY+~xOe zE`xPLfAf@+izKP$_vuUvhNm?OtXzCNnLr>MDWwNjF={rltu3`3>_Cv><2e%(6AD+U zPVoHhIQjh)Jw5Tj?2;x04>V5oCB*&}jn;*(3{GRPJ_R71MKK%CbpNwR(vhSytaBz?xR8jCHZr|LDlRN_M-tX^(I@_sb z_UpxxpWTSyZ0D4fZ5UU`@J~qi)}%rqpH8CE{>oN4wldFp4(GR&P^2oe)m6SR3*2+{ zC)ssnj1|=%Uq(!DM0c+9>jhFD|KI#`zl>j*xITVl!S^X0G%+n`53!r0XWd}Gqq6lY zRIhAz0YcfF`5pc<0`ip3ir;dnf9>B6^>3^Fmv;P1!TzPA|3~N5|7whX^%lro{?(rU zuc^cP_*Ax$c?rC>mcbz~!WVn~0iYiQMG(e>69K=}M3d$0zkmFb!9P3r=L`R@iU(UE zCl10BVnpL0ucl&{G>89!nAAVdUjKLbpG^K41T7W*|K|;$`SH&i{vW-87$krefO@SW zStF7@Oyh{i5H_mSAQD;8#Gt=|!&FlPDixikp=bHqRQellsof-+=QRJdk^WlR|DPWV zsf38$_cD3R{akz{l`;DHb0!$m@>w^`h3NdN=i{|wWTM}+)83X5;=bwm+Lz~)!J)@j z-Wo%0sf&t9UXX-kVU9*-KV?6+RJbhi{P|g>I{}o!stX@KH~btKSwNGT`n@wUJX=aP z_7_Z15WVz?qlY1(N>mBcxSsXkgi#@7nt^{8|4$Ogz|X~K57@-V=LcRzUHQMCe-ij7 zf&ULlV6SpYt9r<&*+HW-d%RNTGL1g{-DRuJX@w-cfoYv)g%+@uI@0V=MCY-_hKI*> zzL^j2mx*xETzEOPixpjQ|B|s=B60?#agdQtE7A3^90VWD1@LC|J_mJ6_?p2SPw=J# z^^l#livi{&&8gpO zUn;3D(-d2|uPTG>20?#%a{Kjm!@Tan-s4hx--D#>PF6j*RY*|K+n&C6803}DV+mK{ zR}jZ;fgAeIn^T>q+5Q(hne-r8(ArIXGFF3E=$)iKPJ<-0c;;>E*BXMaULFGM7x3+* z-U0uhd5t~RwVQS_$m0|sP`5w1X>;&pEOKJI?tpp6EqBh4ezYJp8M(jO=d{8tuxQFa z{e*sr-rDGY{P3z>m7n+dE1z#>e-}eTgGCzcC;k-ymfvEjWLoqumD9|sBC0(lr@<#< zKUpdj=7h*nDbmRNvShxBOwm5$l!G<>eB z8?5&^FJXA$*`B)y^~QBw@IseP>w$D$&w$l3(H;nSAH4`eZjl0Q!~buM3&b6(%~22#flzClK}Zy_Ls(5*qp#$eJgK&yyO3^@j$EWOfFDe z%TY-{&uhpkgeXLr6r$3;y&q;jr%C;!!D4~U`TtoC)(otWMr*(dy7!sV`}axfX@u>& zzjpB}t9Fa3@&f^?Q7z?iVRLG*)WPeM0oc2L8Mw0k>(2gvTKt+Z?#;H1xd0xK*={HZ zenDmHxlfV2?tzds^vGU+Wu|cDim+;x1T~?F{gg-96rwmBn2u24wXg2y1$Y7J>*E1D z0)OqvO@&}YU3boI!;CxU)=xnF1bKdn6s;E~cGRU zX(~OMuf9x@Q&&_!A1LaKdUccC+y9}lgBiVgVURf<=p9ZOYrGto-K^w41{em6^X(vYJzuGpWqldu}h!;e}6o-w-`S{P5%RpjG)%-x}8g-1x8S3gz9h|NQRmO@ z-`ul4J=b?e)0G?21(WYgus0TjsWt61j~)NL(34zQr%iLua`Ee63Qbn9$&h*jBK^F% z;k1lKljE(|l+MKkVtapoNB>$E>{_R*tBp9EK$=~+PD^Nxl%xFKZq|#I7VQjWWmC13 zzEJGIGR`=_;~vLjwiYAj;anV1nN18~oBMM{;hE6l(*Oz#Qbj8-r;$mY+KOS^Sr4qW}7-x+!>SURu4nGexe^w9j~=&2@qdzlIi^Z5^41EW;N3* z&HdV1-w}l}rq^LvSr<$Ae)q1|*KYOA*sJud>3^ImEx*>f9k93eBsRSK^fYVP@85~C z+znzLFa9u&17ErP=6qwY1MS#8$6Vu?T2YqL1Yh(Z`h>utK*+|`)Y8(@I1~0yCn)?? zcF86Kk{+TyOh;=I_=9X>jm}?f5GwZw+B_Fn9qhJ8>FV;C-6_fWa+VGRakC{ZKf*dI zf3JHuI_)=f^rKtkO%^-XH1w`kPwOuGl@=jI@+;R@VOzXVUCc?ubO);-bKF;-hHh?F zoZAO1S}ttK?*ysiE1ql3hmmZn|#xVdfI1N)8fU>-!ZY)pO;(z>;|i&)h;XenZVt;nXLi z)5qO7W_F8aJNrdvMF!k+n9Vqp?+f(8E>3kdbF9s6*z0C2kRQCLZps~Q-1$i8O`p&v zC>ZruxXMW)0&ZOOToQ_CSo`MDm%)0DbP>Pt>Tvi9mK)OHLe}0>9vzfrN9wKhe_~e6 zH9StlIszvBY&&8^TwkQvHMrv#R|@RJiobadXV@o-egT1TW@Di2qKQvSR`b6F!-J74>$Cu&_hEf(M5_q zKdYYnbqt=|K*Q=ouCxu5)iim|q9L6*{8UTzs6%tIpWoSq`hRrH)QUIM`WOV0ws18e(?)f+KO8PmfP^A@Cb4#Cky4 zON)M@MwMi6wBeOqq2X^NS}~)gdg=2WhH+Hdxz)BLM7k&4YBgu4WAssr7hl!jp(<*f zX0oIH%47CVVg=LjDyp7F3+u<7;rhAS(|LJcrt}Di_?>c5GOWJ$B z=aK5A_LF@ovk=Skw*@v96E5RE3h|qM%-Y#laOd;(bD5QE@>w1(u*x@mqVuG5<303B zsk)i(Bay-*$LN9I#Tt2|R#Vm=>rcnI6vs~faIEgDkC0O*#mtEy6;!sbB(IbAM#-uT zf5Lw!h&@)57x*Ub1VTDJTGaK{ELIfC@6ewCw|XgOSCtclo*O^o?)J*t6!j4`+j`uW zx!E*Vdm^*HAG5nbVPGDIVmWu73wt;!KfqK-ylP82L`EXZuW7(5#X*P~d?Y+#1>IR6 zTS02p&ye)&({;-()#J~FuXWAWCI#@wqK(0(2EGe1dq{i)r_7{xh<3nknNJ}-AH>2- zzDk#La60ItgvCt8DSLiL`?>svUec|q5T>{9gHQ6NFq2kF^w_KP)}7Kr>_|_4$GSj< zp9ve3tZ`QF=TiEe(D`i0-?aZ7l z*(QImcmXA*LY8t-`KZ$T37+)|?9oF^XtU1Jv65?>0nCw}>1PAI;swhmg2=O3l@0PJ z%~_wlQtd=s(5xU=V5&osXY&A35`EQfbO_p_3g`T%_G&4gvuzm}aq12i(HHrEh z*W%l%{ezU4UxX!c1i9otm5G0EJy3?RY4ZXY&%&n)k6+hOM@j zwLrC^`8$XFx+1ux+DPq9ZI}aeS)gQd1ac>jGa_tjD!?X)7AM(9!$pgeo{|qO^CX_( zWNLWFB`%17+P!y(=71p)WC^Zej}O|3Uj&Ffn2$MNW_G}~WPkLSh$d!AWMtexI-wSu z(++7M%2Pyr8{Bgrc-KX7pmH;IM*?bx=St5oi63OT0+KMlpDq1cz2Vm>v&a!RXFn%_o+q+`(U>_jlrDx#!n6t+1b`QpbCrmg;NxG zkiHBpZk6$2slWM(?_M;ed-W-?v)O5?oq%+>S%MoZ>s&XGC47II6(_pX*^hU=57JJV z<7mM^atm!^O>HO43YNYJlkZy#iB8bKZf} z@3`vo1)9FRewUzI+Th@F%W3wM`@;?emH@2XKu+h8BgAo%NQo$PSu{WNoX4?mmp$B7 zzg5Laxo96{-jSuKy2Y1gj%-6bk1X5C=1)LP3P)$hxb=E4sWR?tFrLH2ZpQo0whzAsnH1vgHe^Yxv} zIOAS(Uf`>InRcT?Qjy^dbRE_XU5}?n*RoP6pTzQy3&jg6+BBbXPP4NxhxAN8!j`D0 zI^#oj2e%g{SL2p{P2J}Jk3yb5Jwl|{wUwFrszOoR7i{eFEsp2E+F95b;z}`FmUB_n zH_D9`Uxwb9Rq>mD6#|MrK>tL=VrN9Vb&yrrsXu{>xvceS>hJWBN<;}`?_L@4x_2?=7i@l^+Ih>ayEw-hbxyY*y!Ch$iObYaa{+ z_5BRwht{H(-*OlK8J^%T(AcHNwwW9Ay%RF?m=En?{dx^=Pv1qaj0=ZhmxHOVO+~%V z-D>@qYDLhxqe)yw96H@X`$Y9~jb(#ICKq;k(iUpRy+-%<1KyY^-iCx3NXX-(j@CSL zJj9NxuwO1$2#)M%mVW)w_K_<6P*RAVsrfq#)jViIp-P?kts`8r4$8uW^k%Jz&U9&8^V#(cIxgP*4Mww{8U*xC zR7lb6FZ_STK`49U7iq6YKCSYgs_$9ZN!1_OPC3qVkAfb@&aP}ZUt44NADQs0e~+|F zO_g^YZ<|wVFlMnS^fW?0@(;8?Jt>Y6XD`z$D|#Y*FH<$B>v)r5RV~#U^qjs91xC(g z62kD$@8_1`g<^6f9+1xMcApu!y`+_N;4r`VU|rjPc7vzsZ4~16d-P}ra(SCGsg;ND z3G{T5pR1cohQeC3WhC*XAp2T%?&{88RbuH)GS19hgWuoVaGTg2dK6##&BQKnYZjjj=>&Rr6Zvj=21*w!lDLcxBM zdA2v=>eg@>@>pj;r<8b}#3Z`m_t7)lDTW2e{t?a2MXj8>7&j22K&%P6J)frNEpOMg z7T^&(@?v2qVeUK3RM2Tpeb#3uMcjGHp*MvPIdhC7j~Aw9LVZgV@ghbcPI3DU|7ERm z-2k|~+jD38FH_Q5JFag7uql@g_&rSYji>G@8!gR_yuuqFw~3TXPvsvLNApcfNlsYC zq4or#P``0?-V1Kn!W5qbzBhgeHs~g56{?A$ATEDxx=xOhE>6sWyTrF}p>St5d@Gxz zwdxvF;Jr8hYBB`FpVeCejP&6>cIfz182{@A#og4;O{cLmJ$0LG~n+SpkO16S0W z=SZ>)q5Is;q@CX9%AJFr%58V&x^f!o>*d`XduNZfYk98pmEv`tCw1)4H#|(^nk!^` zg|m~K2#BNX2F}VU_M6D*;U%&ogo9<>O*ZOLHDK%vy0;!d?B+KUAwMXz-1j`cF`PWO z5>rudl9#0{=y8i?sEM>>oTv6ne|J~94wWCi&UP#u`^89Qe4ePo}6bNpH+oMw#En3A> z9zeX!++R-Xxj??En&L?-r{)H>(@(d*e(SnFlg^=1A!7HV=SBND0ml>073q5*ws!Mxq)}<4qBBzN) z(r%YLFu`|MdV-9*HHhNuA^E?hON%Xf1MInia-OQ&>i4UCZ({w@ z0QhmIm+*G?Ly1kZN}Ry)wHhhY#VKX42%qq0A}G77SK96FFUXGf%Sh2KKj(P4_;g}whs$C#KIa868u76d6-j)+ z-uFZ~DNol4k!4@URuvz2-a!I=D1b-g3*~hsZ?KG7HA{6`lz*^&-__XF`y9!I3?C;1 z-{M^bA=BBFP`ZD9W88Ku;JTY#j`>=P%8!~es=C z*f><`>9#&k$B4{epIez96rd{_arsfnfa z3=t8geM+ZOmw7P=l@gIA3AO$p2k>t4x=um^BU^o@+Jgn31)iFbCuob3u)wnix_ z@(HLyq$9Jyex=u2H-4nmw6X?OAaKMSiuF3Y{_Xl3X;*qzqS$CWF?3#2;BJ*$bLRGm z*K;;DmCJd;QDtjww7A2NagI&fjjzZfCo6+#oRy8g`fCNSUg8hD*O(#2@#}VG&ZV!% zXmJY;=3T#8Rhw&ueU#?L`K;h*qkRL%He#pe>D~(<9Dlpu#0uLF+GIYaWc1x|e?BhU zulEO2#Sij(wvvf9HnlFYwi!zKUivkkn3wTumJ_fX|6e)}n9h3S%{{pJa%6{Jz}q0L ztq~%P9onz(3%V=IJn1gc6ZaZ#*%(;c(}#6G`a`r+L8`{EF+?AZd*j*jBX%Zztw>~( zN2zyTLd&xRVC%2-JVZUzmh(yH2NcV9UL$3;5bHa=nHLe#vcyh5uPBWnD>mlI{SFTK zpx(KUY|U?cO|z&RRFZu?zNq8UrQ-`OagD=9&#gw6%=rYXZVZrlPFs4H$f>?J=yi*9 zY5T$9O51Din1ag6*v)14@P&@hCcH)PJ-2?Z;^Ch0lV6R9WGJz{dbX30jM_|N8*x#e zb-~)3n(WSYe&b|s_S{&0aqQD!$Tj<31DPHA&l4; z%{;+p7y7+RU6j98o)Xxc^})UKlW?A#oaF!X;j166<%>aHDmyMKHD+^QxjFRAf{i&L`l%_UdRiAiey}o!#da5^pM~ws&~)rUco#@}8PY zEw^2pz+Af=01_Q9gCPVNNB$dn4s z7xBnrKXiX3V`Z^zzca{`6vU6<0zj0&1|EA)lc@OH#+_g!o8P}vA{gtGIj=w$;IbA| z2ltHyesyo^%z<@zT(rVWDH8+K5(Ng|(|b2~mw4~tB0fAKH!0vP_Y!j7KifBEx9gI+ z7|2SCyH|zFinHv}`o%sx#eZnH=#-l|)|%MK49Xh!!nCnwo26wrfYIARCGg{$(CZ7d zcHTutjgUL$OsqREa&`U)5WWynFBPxRRBl zU8QeHI*!03%QWdOKB>E$u`3;g`eh#TgzZvYU?n}OVOG6}b5BzO7w-cP>*m_vW(WSG zV-9)3)w*>xY42j8N~!A3?N`;!g~yd1a%_5I% z>x;Ckz~kR=g*SByJ}jk3RSu9p8;;~#swvG_T8}UWC$I4P+YZn6fL7O|r!-Wnr7O6} zj9!bpzoaOp;%4+vvibdtw1)eofCpc6)9X9dD;W$++EzCf`ib{TUtSRqLn~JZ&t&Wv zZKN2*UpL0|-3VtZ$vN)UfA+Nv*R2HJ?E5(-Sd0;p7yeEM6MUwT1s2_+TMTica=UmZN|J2&4UB}4LUyZJa@XSpHqhsT^?C6*E#RW`}|UNnsr zCToy&W3|$PwxU;do-1|!M0BHXU8Z?-{UzSOr>%0ZM#6QoZxpx==HzwNmLIuvs@n5m zVrX0GyoC5VvbSy!3-k9NgF!4?m;1^)Kfb0X1R*y2gQkY=uVJi z=pU-(nDYdz=7(I%L6GOaFDX1V+B^GoYmGq-;rr#LrZh4c7(n2Qr0k!z`%z%q&=FS^ra~_6yjcP^*&MLlQ^a1 zkZHb|(jFI16tX?mbyVnVn7d|Ukue=B?5A5y(%Od>+?JJPm;2=@%n~Bjw+_V^5|*2X z&%NR9yN7s~bsePw6g@_pck?)Uy0C)1XosceJI=^gIxY(t%hmhcloBdq`iSeNDZ)yn4v1$(y%l60SVK%_gAy zy@8F57A6m4$swX>P?gFBMhtP>#OO9$(X5`2AipPd51wW}bzNfO{5<6By8&|h3na#~ zJ0qPr+|mUE9vTMwW=!OXr^6U*^di;eme6?)y_u`XH-^W5OQVV1c## zVn<95=2qljxI4=gRo1VNBrC)L`*{?k z%QxYry1N(NGR%9BJd%BX54$W;koFSE?j`=9_C? zD*leAL!s{E;?~}vYR}u*8}RnlPxsd#Kr9su529IM0tIj16>)vI$>;X%Q4*<1*c{dM z#X5{y>rE6c`RVOR&tz+y53NrTYU3)3VjBAt&8z651A3cQ?anU1x zy#j%ub}t=gtU*#;h`sd}Kg@1AQ2(>GZqrrd!pnOB^HtfXhg&OSN?lm%WRDZ74?3Rd zZj8}gEbHDT+Zv6}(xbV`!ow2cEK9Uu2~9b-(;L4G_fxqFIpsW6R9!2~4cjlfsh&mM z{BGhx8#X@uWbN8jmT~rSJgM-czM(wr!(~)Od)}qr%9#Uw-Wx*C& zT&z-HK40dxrc7qpZ9ZeZQ7{=nPJ0pl++FWkX|t<={?}>|fCZ=OO9C(mNB`E>u;cUY z6Lk@|_w_*+R+qVfcJ~kP_UNV2`aRcHxQA9jj35BgU%mQrBc1ZzW0Z2_W(t_N;NW_R zgP2hpeTRE@CfL#!GR;{|MT4Lf`{E-2$*#8ky2NqsZdxz7=icsu?9H;98~M3@=?bEa z!D{Xu_ZqL-I@Nw$8(|=}P;w<-0}wkrkU$3Llw6@hS>ld^6uWKE>DErN);HN!}mi?npGxxAIsh0e$$Do3G(I z!wd1pt}ooq_bh-y0&S~mX`9qQ7 ztu7&JlA4LONAb@{K->diMw)&JU4SxP8Ot)B+p(a~P;4-}sL@Yq!i(Y1veIt^@R@#_k&h0P}w4on}G4prhgbu}${JsIsNt}hm3n3~e9h$C@4vYC2| z<3-uptLwk^q52Io*HN5;5AeHUFN;m&1Fp@Ve`JQwBYPb1miuYzwWjeE567D*!SB~= zxu;4rK#>K|DS$hT?+H&f74T)LyZ74}fTrGw+6Z z26Zsu6|H=CUicxHpg0Sz3CPTU5QOAbhaR%^kf%{ zmLBKde3{m-vva6MW3;BUVTl2?yCAlia0s}Mbi@e(9hhTq!S!y!4?P)rw7c^p@3)Xr zJ?*_i)tOaiFCeZRWR92LnP5~wgQ}~@QuX`|m2+WDme$z;)*n3h;;SEy``YZgwJPW& zm}Z5X^>AEy(6;t-HM!H1@%^~m7YhDTe4>SUh{l$;O$l5A?;mf2`vIxE@NG{;A0%1w zE%$UoNuQ#B3m={$17`Qd@2aFYIs!>dTS(6-1it^N!v8w;?impe$K$6*eBTSwsswI0gp!hsFG=C!UY4+(+{dZ)2?h7B z{?JbVU81#*>tj=zo?F}eroG$kI*D!Uwk`{W17SeMR-Yg@hSb~Mo@1`o)A3&L1G~+t z(O5s>?GJ?*lo#ZvH(84*EO7c5HR(KGi3lo8k9^MpnPkCt5IZbBa(W4z?OmI*Xj<@ApgH3qaag+@b!(Yoc3Zsn{|!q zcKl%vC8=|5qKMxO6F|MwJ4=}QxLz*PU+FCGR|1!wNwwTB(r`KF;W&J*t)feqAH$b_ zyI@Oq)ym=}Vg$>dOtv=TIrf+hR<>iRy>W*3+j0QadDyw=a6gR2C-^5MI}6)M*nZ)n zZbvqX$d8Mry|1F`NZPF~E7K-U5Aad@izt-V|wBTk0?`iWA&mL|gUW-}ZdmBF9{5{tW}CS~S!xb1a zJ1J~MTGNK{n-n?p5R_$S0{A~^V1egc>06mD^?7=?9`03qUbwgn|M{NAv^SIVbd1>w z3v(!3s$l@m^=J#p@80tI3fOPB!mrfvUfgh^a=V5&zPrNoxUhS3=_e!>73f}g>vd16 zg8N-@$7g{3O8=fQJGPSFKKM^laVAfw>}&a8p11dh2FIPx<<*y%kbk$)9^-w*U}IiA zI27u=mpfz@QgTzX9!1T@lfMah>=$YOF7a;@3R5N7FrfAo8KxgW(-UQ{^}5se)4P2E z6btp1*K`~7j%F0!X5isYdD|VPy!Mn_+5xK(NPB{5nm_jqUae>P!MZ8AL(}VT^=*^V z;wh6hum0nLFsiQny3zEG@> z_A~nVGhqKLy~Ik7eCH|;meSiECqLle4n?HD3G`gj%sE{IR#A4*@Ms}HV8JsUI`?{( zHjLGfILtE^feUFUH+5ZcKPSC|)dt2RBBR(A6tB&ZjhcT1l3bgb`kpk!jWGt)FOq3r zB-UBIHI$SgcI9xWlHe2M*(JWOgKfW93?;e+(Ip(%HLA&ViS4im!`%)zo;Vs&4fMY(tvYz|upF*r3xn?Fj<(`yOF5heO3qQW5 zv-vU8&RhlC7#t!pg7u}vB^YHH05HP~w!T^g{k-+`=J)Ib-S2?@Sa>Q4m!&i7L$3J* ztY*^;%!P=7!Ni(@MZ^LB@}a%CQJDpBjYP%~ca$(cY}#UWJnh}O4~D_CKTNe50&zd( zE&JN3S5iOAfDI=5Fkv`JWKuAQU~}qXgsOAkg?D_oj==rJ0u@o)>Ff3WPO-JYiXY9m-W29(PO#yELec334p znDMxDm`Gy7y)h}7Ybb~Clv>J_>O()Hp17_X*z!b8;q-KQiG{@{#U?Qlj0z3#IfAvdGS zj^znkr#hOeGv|*~M)6 zboUe>-diFV2iizO8jto4+;-=sJWz99zMzXQ{a zC>j9pp+!hYkGAsd7);LK<42bRu#S(|`lhV*eA0jT@45;=y3DOD%!$_gAl}fU+3DS< z@&Lo=>DPc{V0RfqvtE@v8IRQ|kG7QCb;_2jcj=7fVm3MlibTVbkmAoRX$H&XPh&L7 zumE(NJ^D>ixD*e^dyF(8-&maWICdSQ`o#W~7)KX%xUn0PRgfv+9cM^6t+An=Wom$} z4>HLn>_jiqqn|j24EM7&ap&>?7m?ZjLXLsNeI zM8Ll@@tIRPX8Iit4?7S43a?V3)bvGoir^96T!SGzoRW}E9We?($DP?No z2-qUOtpzfp=nFrr%Tui1r~sKfqjaVD$=iole-EDiWd1&@{6$K7|UDUKFt344G0^mg30RY zeUHVAmCI%8ln4lw;4ZVGVeayicBxM3rX6cFckdv38gICG)N!2maLm>Sw=5d~6f5W9 z*_2%{M6%iSDz>u%skjD0+Q{t#T^uIL%_WH$f6Gvk3`3Ch6Dobul>hFz!F zk$}fxU8|m*qyp-Av-1Ve&*Xbh#76)V^9D(cu?WJ=M$nFf$$#f zXE>=UFsya&QNDh|^~*ph7Pl7t?#KS#eQZ64gY^ZMR`}KR9{Ias2yGFXI%)1|o8ba|05=&rO8Y`V7a)PSO3f-$zJq_dd++WVb|1 zmqkk44VNwai_*k3;d<3CmM4H3m5pH*>yJU{_pWJI(zCME(XY0~mU|jjMDQ5Nc&zl~ z5>?=!tI-`(&!5tAlE_>%Y+1&+pW)Pdl#0=b4O#c6D?ujwY=}ZvQIHM;1?Ea_Ab7ug zk5t4DU~B4yWWAWL+t%da51!Ci+;`9h=f3;b7j!Gm_XN8hRYL0-7lfS7+7gjg5C^@~ zV7dnRW;pJoO8#)+~o18V<#gcUQpESs(;TE613gt)~PWh?tg^Vfgpc6 z&Y!aDnP|BnH)j-OE2V1o)t_RTJx%e0n;cMTRnd^t#Pq?a=RO?Y3b5Ylz4X)0m5Z}; zP^Evw{6A|j#MYTPu)spJbCGTU-qh2pa!{ZmkftvcRc9jw`G=J2g^DD7!^XD1+(6wd!-0n7A%G+^RDs2^KLqej$KVCohowBbuIRb?tInMY`v(!} z4^I4Gq)70}`sX|IFwK9(DbQX4|#O(6@S`H)4J{I8^B{q>Tl}dE2`STCJ+$>nt@AG8d4)9Eabyd;%PX zdd_>8IGwM5?g+6B_~smB?~`_%-|BWiHbLpq1_7YfF0()DZ85qL$4}_6-sb|{;zFPI zBZ#88Lq$o8ZJu!*V3O8_O6a=N2QLi83WQ6HssxB`@qJg-bF~_M_n?53=!QSpFgujg zL?v}gGd%HyI8g0p+O;48N=qWRL_opM+7_H^(WD*`iM`-u~X z6I_svt6J=T$-;8}XaffOd%Hna0X24hB(0ZuuPRtp)`sVpXn=&OK`j${2{=Oo;yVlM z-8aHw6xbQ|+xwL^7tQ;9-Hoa9U)KN8y7Hk={~Q#@2S#t#j0+6ta8MD;woHe5`CYd3 ze#b>{w2Y6L!|jgp?fS~0pRm!(<7(&8!a7V~?9~P3s|P=KPoOOy8Dd~@83sQJDzdX4 zdXFy|Wof*sS_7%6ns%;M(1{(_7jo8oUEh`E^Ze7O=TPv_>Gv3G+pHgSxO-ae!|wN# zZ|thweDT)OYsaD7o3swoC98i@9S4opTsM*GP!M}#TPxllA-)3p?8~vPJg_gaaz_*m zFSc9ry>mLT;3%JPOr)Ma{^xJ@+nBz3&G2E!Yumir*ResMX{l??Zpwtb#T!xz=EAT4 zjK#viv9#;_AZ>Ej&(hdJhd6@bF558YXW;cGJ!6v$!FOlg_8>dBqDsR>7ymE_c{omH zwI)WbA@_>TA0lFTM#W2+>>{yk@D~I^ywdE%p|YXNJbToDd^Yv9=x+pBm#Uf|d0;!8 z;wH|U=ijR+bzB2|z>O!0J$7K4x)V>b&BX9h3?X`#LzS>6IfCE$IaSpUsZ_nuIK=Fs zWskaAxo7Vgw;kgVls<>^+YhO882d!jI;g2LFM=|d^@i&g?Qm?J6jc(XtDFVHiyeV* zkwMGgvk^NnliMWG-AiHn!;|cw1OUwJiGjbC{Z zp}Yg>?eA~$^gh?;#8DVi1wyYfyBpG>>p_IQ|J zP!D~;GsyvNdMRXxr$H;#`x zQ}l9`n9yt-KyuyWBL8JpqvA7|82*t_K;^awIDH;K&vUfnba>S&9J2{~e&07vqhLX7 z>9w^>%O}C6fYYCmoVcyF}C?;EJUiaajh<3gBeeH%+iJ*yU46 zVgX4NrTc9+7K87+AQX3$$qUaAJPcoW`x2=M{m39613+_%ZL0AzzckM;FdvyK$D~Ab zRR8*T=0p~h-1`0Pk08pKC?HpB03S4KCo-bEi9%D}6!Y=xy%A4Ec6E}2*ld~2MEyPw zjn&s_0$NJOAKwLDAGT65&w7rxTl?V3sx+0?<@5Xd+X!6VlUJ0|*r0wO<5asJx4(2INDa z2xq<3w|`#Yss*pK1{}1(z={)$b7v#@20(EOvnw{7tN#)x(UNk~7Zlp>F#%W4wba$S zf}O`|`PWI%DgbiqphgR`3!hdkTcBj0axA%4`1s`7=JjPl(}5I4c<-ussF_mh_x{@| z9trLyd)IS2P3FQ>m>&*%6B=k23iLQ3pgHAZ(YRO8eN1y?_i*@Ok)DBp_kI~2>YmA&f3$?aN`&2+fYsP#Cq}DX#&te?6hBrQeEH>m$uDtb%(6rF}dOa<6H@O|zI>!c| zdG<*XcNRDm$ZXb+Yh<~LpBi8t2|dEbY&EfWBD?eWO*MQgUO(TXbd7+R z)XAu^HdB3b-__k|>iBIZi5@-Ac!!>k=I4ZTZ^G$6x2Go8Z7tam|YL3@!x4z z{~q`9d_mBeZJeP(MQ56S6w*TNVB=2!gdlKfPY8e6$tTxw>@*|Yy*@dwu^N!Qc6&k#x;8Wg%C%s-v%AyYQ5!j_ zv)4Wqj{!!Pj#y4Ne`ti~0Lxnd`5SPaRrYD;QK9)wV3m7?-6`+rYx{LQ zR4I3Bb=iP4Pn`a+`d*!7^F;VzHux(yR#g5^q%ua4G(c`(v}6ULGU1*ttod#(0;pzNcq$unC`Iqh^Y&>MJATK8 zvTw>Bb{q;U{0l17eNIGXY=+{WD0ZVP{k?kGj&opYM;b2qeu+C68C*V{o)H6%>cT}@ z>b9#T(}!7ra_Citw*CK{8vrRYkmG7>XQ?N={_=+X<3L?1e=&f+{To#N790j#+>Hvh zVr+1nxu#f$Z50NW`ww2I{9o<8`9IX}_Xn($rAQ^pIxX+0Bq7;NMM<({-(}zTeT+$p zWJ^N!q%v7%EZK%J_OUNn#!h6N>@gTK?rU^EzW3w)A3ozJ*X#8<=Un@B&Uv15LG{Dc zF9Vxs*vZmv0m#uU)<0i0?`^0XV**S{IPG{b=EhtAcD_PV0uzyD-gZL*BXq$eePH`_ z-^y}N02=M{Q<*Cx`U1t8Q zzl!71tw1Z{y;Ut0dU_%I#BuAdMlj=yEZFjus|bz5&Fkq=62J=_JS^SdV6zz$3{`NV z#@%^XD)8u4mAOjc9HaM|5jpM{IfXo&Q#sK%tdZL!bG{*MCJ)E@27eqEK@W%gpD;-JDC9h27wozXR>XkY zMHn_6nYDs2Da@QG^Y%W$$L0j&FNhPoaeD6w=-cT!{IzrM7A3YR$;+MyxPuco_g*{? z=$#qbM(Pd1x3gISoy(r6`9p%_>PDc5&*pw_*`lK-7g=S*Ub6vE?V~KC)J$sVzGL`} zbML+mhU)N`cU^No@c&0Co9<>3aFpliWrX?NJ&@SqPX)G`NF683Cf?OJi?#itQzmw} z%pqb8oVvejOl&y@6hYluP1tjWOFh>?4>+yAEx6J`RTRh~@6N)Zv`&bKI!daVF$h!B zy$L;-oPfU=*3~1^^B?{BA@}pk<2ss!<3dAgOB~lfzj-NRhMwg=L zMUUPmtDJ5cqP{u)@Eq*q8?z%EAsxeZ4>;cOKi)-S=U=I+K2F|%!*Hvc`F59;Ji82? z5Q6jNq+MUBu=!DeF2S>BE0VT!_}V?2CsOKs#a^pN$1;7I zOtU!1lnd)7aWHr7-xm-~U;}9$2A?53%r+!9QcHec;y!Ne@<~b|GH-1DR=`t5GRS6e zkuCZ7id#Q$R!;2{u(*EQ6He!b&>Z~=bwV|h5@Ak%#u_t-12w`QfS1xqUZ~0LCtJwK zIE~WtqKEl*%PcFY>z^NOD}(jnmgkAFnH&Axiw3@T-%gkUgD>?|0XMx zFVNevWa2F$_&xJe07&yW$sL?jS0;AwP3sVALS4lx6#remxHGh!9VlVs4P6V-!e5$IB! zqqX^AR|eW}R4DVIodT|*zeEe+(JdC=<`xEbh|A04SBj)7g73Hq$bhKcf*D)@%#w7& zeb&JdG+!ij`(z(3&fFv8(UH>InkMhp0?DMXZ6%sEB34~bF)?jd{1Zvd=e51@pygn& zfY}%koH=y!kGN5{Fx43S_)n#&_4HYwkC>);uiN$}@c`7dzIUfEKiUvfwYRC28C|;k z=#bu{5Petu3N#3F=MmexJVm~DYV%y|<}ZxIbMcM2#s`bB(bFS=o|b|_lTOpm_NPAX za9#=(mi(ZuR_TUzv7u|W!ph(L<+D9vOT~WQvBDtiZn%2)(76*duYuz-ON?2Lmtn_X z09QxfaT$p$@nX~d0-x#=a#Wb&_0GVc8EZ-Qq-jR{HX{fnA0<0Ja}@6jRNDw4TX=X7 z+7fsiINy~9G-y2bFEAbH!AS}~sSqrewx_Fby7s{o1n>+Iyl?GR209CDdcSv7%ir)< zcM!0Iknk=GUMWC%)IMu0eqizQ8D;8%{?Q!141T0a#O_9uu|8xe9@mDYICmZ`9xV5a zH|TcN@J$znm~T=i{#Jb{*P(x*>&Y^f?V1J_BTh`xUlXiQyqhX-t}(liYsTLD#yQK8 zFom3tdF59*E4sxTB9`2+g3CSLI~HnBJY3(d0xglv{&3v?v?dI19CO~>ThIG4N#;b2 z4c(`|ceSq;JI4QOmPUBCFukJ~T(;wiHfGPOs)EfhAtPVSP4i^mK7c-6*u5D`@Dq$r z{KJU1I?JYRo~5GF)tM?bgr_7{1muzoE|Rvk@rx5f)umSSCo= z`yUZAv>Quvmh>sehvtom zxcugCxCj3*YV{SWcg`1+M1eD7h?0NuFV5IfnPj z7~XaZRMlk2{4m|z>s1h5Bvl6#V0rJ*<(~*IW~HrVnSe(dc1t`t6St&iH0`f&Ics;P zl_CH8^>itzaqFwDh5k0v#xH}O#h)!*d#RSKO!yDrsLF3e<&6nCTzL1DI^EZbLZ3wW zTg>cBULXq7MB8NAy^+ z&@B7KfpW|U+P8;JvlXaIl=nDfq{9MjISDMj9A7zXjDK$utFCJQ=#&Wo4>>{-(#CW* z?WNtZNMk((m0bVy_o>uBph}wRl?VgGl;PMxj|ki(-{Pku7P_?w zK2!WYCGv0kG&A;FtZl=lf4P3KQt9dmVeV|j6RhMZ4<5t@kl=ng=<&W*RrtJ zB73kncY3>vP)ZXi9(&z~owaNtahq&Po;k%$r*~Ybl5vH{J5=$JvdM6jV!%d&ubA_j zip7;LAHQCXlQ#UpPh5naP1y{6s>8r(QlvP2f3Y*=L(m0pe*yhUHowlQKp|H8VlmA$ z;OqH*(9;q8LqIS%dkrmXPiMe!df<{f&bIBtb%jQ?PfRKrEep0)2Pi8Qv)sUL^0B$G`Kwc?^M`R+gY?sVi^<4Pt_hmq95G$ zz^}r!A9zuZ$A5?mnI8E9mFp78_^uMcb5kMZ;go{GSg!<{atAQEjV`sSANB5Zu-2F> z&x{95Bu??t-SA-^%SNp%E)G9Z7PJnFXP#J%W{^-^7c>_Lbfo{OSLHi8cxqRz5aHBWA4U(UF16~j}fLWSjk^Le|@6=w6@%J6grE@lR6q@p`N<2kv)2sMCUr87z>Asyc)lMqoX@ZPk5^P7`b{`F%b#sVP62EYugc;BFpI6rp;p9-#)e)etCOcEQ(CUPk`mAE zffw;?99=}^B)RYt`wJpanI5l6HlufUuq&|DiOh%O@wTq9x|>t|vP75bn+zstUa6=5o5(Vz-9^gCB*!bjZr4 zC-q(PJ?;grb~Z2y!t0BBu?}#-byE3r#?y4AV4Fcj312snkO;eMk@g&cXny8ATkP?- zAiefakeynr*I>S(apP*`fLe&*0b5KF!EW!@LL7HOMBTQ?#)&veU{=Hz; zo=jmMQMZ-Uj?bcI;ceII)<+MC*9072nKf%%HjAczFN&i~8KfqDoojE}3LZr{2(b`aH~17K8w-h&H#Tv7b%e zd?+BGJ)6kAxWZnQiy|8N*L_P#h^XH76mc@tM-XSZ8UM?|Nyx5CY}VT1@T|+3`{WUe zU(?VR-|_(3*vGg(10xrB*(Askj8gsRu~elr|DEyDHc zvq&%UX@?V3c&}6r1jFcw>&+)u2j-2)=X9HHU5qYR%s<7cF9?6>*>!dF4o2B4NlqY? z^dq(43|)P`4C9IoD|&HBXVA=!7$qx4JXrO)MV4?h%7ta)p$N^(_(o5n$sEI5H|s*e z_NPZS651z=M~%LX)z>AgZ1t9VKJ%Grts6A#9i4F^?e7_%Zyw~#j7fd6`eoQ>$;rcc z#Mqr<-q5(M^lQa+O5WHg+V|d(JF*?Quqm^Z`CwPBsKxEQ$U->5Ul`q4NTPm@uN=~| zOzo9uQn<{pD9Vw zi2vHHN>y{>c`%t2)8~CvJO0>V_QD}wcavo+DJ={0tPyUy8(Psf4kmQ=WkN|MZjR^f zg-TptSLG*C!(GfWb;yE>|CB9G*-iU0`^p8 zyRK7Jtg>>JM?xs)-e*b{3q7e=uTi&mb+C49u&CX4h1(t>x93NJ|NYSgqJS{iJ=@m( zGM4|hBxoz$xszi>tMsn11h|9Dc|phz&J4YvXiJTV@403^~7}y| z-Reh-#7lv@pCdAI8rY!w&q+nW-wJX2%d;w?+V3mlMc@Vk_ZKp&^iX4u8^nqVhb{;D zI(*9_x80~tE3sCMeriz3-{H2ZJ$|up%s6lh>-=D z049)Osn97+ zioqVqz_4ON5kPq(kSl1jdE0k(Hq~x8>?pBZ5AOfcZs}u{LPnR`EA5XYLP|`^g~|ta z!r5YRo(VA9y+}73eoPYV2CjAN>uJv>Urw8a@)Z)Jz2nrx_ic^=Ckt4_-wrpZ-~__0 zW9F6Lx+N>opu%%zz}Y;(k}vx?T_;OX$_1d9mNO+l+>WjYqsHKmtM4LOD#{U_J}^pS z+4ZW2Zu(CxrWD*`OE)Wy*-~sJU!ipU#l=(UybJxmhS^xxy;rtlzgAG$td0(i%u&jDV=SsKRuKmD{`HGQx%!8n*@y$6mgI|Rmx$`s> zj4vw3^8rNi%L8%R0jrVd>Cgp_&fxzPUuudL*a$v~tHxVN3x>FP1~uvQx9m@U=Q^I5 z)R=g-%&uLflA=rnr%OrYtz?|2@6V=GA66EK$?FFCCgJQJ$q+J@=r(Xu#GpDdB=w!d z`lqP+?Tv49x>sd#i9$!` zCYaL-g!JWOb{Luj)^To6fJyfIcVt{mLVPY(VUt+_n&-fEz1)iHT8(jh4>li%SIJd$ z5f^{k25)YcKWMkG|5V}ofqd2794;ISFdt5NJbdD(oSqZ0>w)MNd#v#pL0ex!HH<&h z!)iJ$&>-XY5gK~ZZxDL9I`BEWods>j_#nvV*wY|uIeJD?FRy55#Xi-OCm|xGXro&M zqTDgu$NrJz1Sa|TtAgMoUEh;mXP|q2q|FhF!<9b<`b0kX0i=WJrHuB%GIOa$JOcy6U&3@?u+{cy??o}jY_j^A;| zfd2C--iiZKt!pngtQFO2zOQj9dtVvM*A{f|+P(=<>=;R9|F|ptEh{F(o+y!g{0KqU z_}9Bw&jX4P2>lw|_1J$}G|*x6QF8y)P==)*!Gf6Il`%cj3ke}(-0pj9wM4v=%F=ON zZ`c#kFpF}|^HEGB8Xv3$r&Ns0xZk&>?5h#xQJx1^UxdBVKT3iTw$3O)w9R%+2{m}L zWm(^s?Hm%!qv(&oy15;*uqQ-X$gCAMG3zswoEHRY*~zDhteutCsXMoQVYU!{^is-b zVDX%dwRQWu1c`#YX~!OR3`2O(j7tL8IAhYH-dH#mUK2oyfK)Ts4S3s%SpSOFl zg8R=vaOVAMTj$9+8gDbo1?;;fMhzto?)ExqI}4T+IUrr6)@Jq<}brEWB|P;v~?oA9W1B zY^qY~rWzjfP`dV1`;{>aVcY0!VYFHWe|HX!(&g_u`Nk-1jVR&{sp5WH>KEKm$HRkz z{;eL2Sr4q1H;X;#RP0I_99VYpxfv|pM{O=LmTR9(1@I?FYvOxzyeCHrM!bmPR3 zf11c!{CeupeKLW-%{Kq9&tG0H|DHq8Y@{QPs@RAc=UZS2? ze{1InWih^8eJJeEkDx?U{1G*5h{iMZ~q}FQGnj3m(?s9~9RS~7r z_RPIi$|$jJuQ)P3v3`Ui ztm`kWX%Y~Nr=lNqj@%<+Sy{$bi3|ynG0sq+2dgUu-42$QE+a~P8aY`b$9#80Tnabz z9Ydli=t0z`tJ3LmJmyFyuN$nI zDM~XM4{|N6mZr=0 z_h069SSAlHIAA}mch?PGUj4K5`>11Mlu2AenL4d%(h0vQ{?{a!3Gg9de|rzBo7=h`8iKj1>l zJs%iU32nNb?vjaeos)E({!f(nZG;=;Te3!2QhI+%FW%dchvxd79zXd=vbozRd< zk}}yV-3_ly7~NS}#U4)C`Ln6L^0AR)ugv ztX(-2qF|J~B5{4!@j^Xwi2jqQiP~DzXnZ+K8%(yaCWP$N(eo)VX`@-z#YK;dSO`6&@!)HdH40o3VK=^SBp=q^n6Jdi5`>d+70F6BG z>R6KUZe%yZwQ>`#o44*N1J7g#c}a0VWSn3nO!AFI;B?l;hKzX0zR-THc6s$?IP%ga zBF0rx^PtCyFfEYzWJ+P={xm8bzSbFfNYP&hLH_H3tMr!_(eW<{00J_N>6=d)Hq6LB zT^nn`Ty@k?zDYVa+V*GURVpJs;3nraYG`n>YR;rM zS~5ec5S-r4y06-T;EIdxxYfOJirgIg@t){hn=^O$m+Wm=o$5uLf?38U`ua9cr@JD7 z5o&SlT6?%m2zh4-GIB+I=bY^WW{~X0k!8~B&!|Eh-WHZpc_`d!RoKe>bGLIx{=B$| z5tZnRYfEYL)ppC>6?G50B}r|VnGgEJUZK&!oav}R!lj&~;rH^i(4$oh#*F^iTsCka z$q2&snGX z6OLnK3qkixCa=il8K2$gVNC$&tKu#-IzukQ_40IF;JRt%&l1kVSbubF)d8YK;uq?^b7|3}9C;@*!qet&g6s)2sU9p(kqnUq*xnU=Nt}gC zW`mY8U#ZJJf|EJdN!#o7a(kY=EIv0R)e1VZ0naHirVjr@ZP-o=X3ZYWH(mU#UH4~|LDWb9Tbu&`tN9duX z2QLX%2wRojDDQ*HDVb3j^`Wk?H$F4#qRK*!z7YC~Do^8BU{DFF1sz{;97J-V32Yrv zG>T!|H(jGL83X%1BCWQH(`UAD6E^|)=h(`X5HjQ7{O$++(B^{!o_b=wqv5egWhrj1 zuM*4WCH9)}3Nmeu3Uc7@_YZgrjc$UF&Wnk17q+I;f@R6ik+0zdwH$ z^7q(^!z-=^7~ZLTXdj@DpZN`vFzS3GT|7#c86ru^Dj~A>X6Q-CzPo=bAKF8&P-V+c zmc|!E!eFq>xog&Un#xVeVgRU!VYD^qfn?W6XU&hq03~nZ(YvLHUk}-kH31gt0J6k-YOI^5 zVE%%BwSw47QYLd{JW}>^%!;R%PIC_knDDMc41UCt^)Q*9j64zA{i=)FoO%6PRJ8Kr1+xr->t5xfwya zn?-sx@ESE@ljeMG)7POH-&SPQBExfY;+JA(t$dxPCjt3-F4ti|rhH^0*KJmyG<`Xs zfN$o@&ol$rYR_sjVOR8Ickz_n?=O*(#3i>(S6@&GHIU_oKXRT&zeeFY$``7?BabS* z?AQg8h(Q+KSXDQ+rnCJH>}bTYZui_D6d7~YV5c&T^~qi8pO7>gg{Gp72pa~7t~Wdj zD}?-E{=jE~6<*NjxdCW)%paqQpK2>!KY%l0W>qaG*$VV--%(ORyr9NJ(9H}lnrZtC z2EvYLR;ynt!sW`Vd&*75HT$Omci64;c=<{`@%cr59!?C~G6eDhwRuEyDkIHvbqr)U zKhwrJm;Xq@VC=$HGODs^CKbEQsS#l`^{tMz^19>hau~H8;`qFKtR-3}*0&yDt$L8P z@(5D)c`eYF3VuETszV!9uwJ zRR7Lp_t?NC|7wOaERfTP!OT~`v!AuM$6t__mydP#7VVuL%19aAR`vMoVV7>D!(vhv zU=l)dfE+u%qSC!^PgLBoWz0sK@F-p9bz8n+C7x_SY*-I8wQ?;+1Dj6LvE@>P#)$OZ zKF=nIAwj~Y_@56e%M z3|?9fu^(c55CSU(PW&bA3kp*8ttB1i)A}XdhQ(h)Ok>$qLz{>o>u$1(P;p+KC4$P< z#QZIt#Zys-j5!al$pUvKAc4O{Kd{EL+B|k>5db~zsI-yO z`g=iNRG_(Y&3n{jGv}3XT|rg*ZVRPIhzR^XlJPUQOU^wVaQ&l_(YL-78D z5P^=~;=XtY};>YThe;ex&85 zd+hQ>+pA}XweY<<7EPhtmaU<8K>QFb&~h#Hdw~U&jJesG%~6K>@rEO zwaO*0S>sBBx@NsriNw)HEyXb6SP>Vm9hz{x8QS73f2HOw6sjOKJ~BiJXy^IG%Cc&oVx6IpbsoyEE_X1hbrP+=P||iZDzCha#Z@y!YT@0|epv`EdMUwd%kkrt>SiFV6fk4;=CrI@$nEJI`w1hTHxqbrh2YcjX2TCAIBRIv2B$wG9XQ-e!l zGc5D8*H#>Zv!oYnAHH~g-HgmXDtshA8^nK6dCeMVerrhVR;iYGH0IXKJ4&%9F>UD9 z+*ewnuKM2l>5V2JDTUSQ9lpz4vt?&P3l=3#_}z;BH2VQX-R5~METI=Xi}XKvdTAj!vg#4lfeb5rzt}bQr|Ms3(Ys7p&Y~x)hY$QfzUe_EaX9a zXlBYN(a^Q>Y%Hvsi8S#n%znM(@!wKYetoq0x!6_JSv}5mqOvqMa@R;w{#WVBX}-&y zlBGKs%2ZFrdL3~&J@3sy!cKV8uO$yXb3a$eZ%4B_Ym)>&1Hm9;of3-;4AVe&bmqy+ z7Xy^{gCg9E>WF{Grq@N;!W+|YLQ8qN3nrx0B0ZA#HMgS^d)B46?fqZi98j`wgyAK# zgk5W*M{oTR(QLe5Ov7mWDMB9k(GQKtnT3LZ=(Kh`TP^anCct!0A{(5>z`1vUsf;ap zezOa?tA&fi1XsV@LhAKPYh}hRMWog$IDyy@wmXzN&IPHd3o?GDz-*L!DlXa)@#|Np zY+aYbKybzt$Vl_%i3wS)qC&1}M6qj#yk*yrACQFP zF6&xGzKJW)dp7>oA)tf8vA!vi&J&b`UlLuHqvl~qZ>&J(KEaSS9t7Jt^s@8)O)N5N zT|RNz^3~Gl?Sa;c8b`?9hG@bh3%$itMI?ari!beuB#~>C<*#JS-=8&GJktizUNMH? zsGj?K#C2kcj4s39$j`SH`ai3un^MU;l*zZ5g81%OrOp_02q%<{<|r?%sv70F9<4vI zCiQDMV{Ym6un@XX&Sw3}BUBi=e#c=zcWxk>EAqA0Ie$hx-W|RKxTS)+PqZL)(1H zUBMTO;hDyrh+R_p2KMkyY<6AvoW9sm@US1;E`uz|j*)R3HPCQX^-d>$IYqmLt{0RS||>Ke-R-mT@JaT+Tmh$7Zh2Ciz@7fjhhzJhT8BDv-e|7o*>J$$Gan-CS6#~6J%jAE8S^+_AoklKwM>%--ka@hBXq5tL;JtJu zBxOs9@WU7u7+BVO2^7Der3mz)T>?>HwOnzvSHdOhAL%qeI;OZRW3H5>1B*gz||0d$q+hpQB2!ItC7Ge&ELqa-Q~E@X6l#wQQa7uLW5_a(S_G zt^D&z0+<-nX9^0!wEnhd3Xy$RQen8{rES_UT?Bvp?{mpgs? zqCPm;vArpsUQ!|yLYEFDY5)?MG;w>l*GsCx^ZtnwRm<1nPmvMs-Es;5jUE60mG*wP zu3h%lXL?SFfEM>TY}XLbuHT#pJnR$db6fvd-2<$YXZPMMjQI4?4(Q0kc47r1z?0pn zA=@(A0Q7e0y?>H|p0%!UWG^EmiMBtVYsdv(Vly zv&BmNO8v_JZ#J+3ZQS!Lv7U6+v?Ng@Q-LD-PY4Sj#Q7otxf+%-c2jgs&OaGI7m697 z)t~swCM*6M7r%h~r);~@Dt+=#N`k;%;YDydQToSCed^S2i;*7=`2T`@$R3AuL9aeG z)6nQnED|tDUN9>CJZ5Zm_%cY*-x^tJp;xb8p85TdnGT@bc6k2DXKY|*ToD<(N~^;S zAec?jsp>1g3Mpx4K`^lN=7qYSRIQTuas+gZk&>(h|Gw z;7_+`GsGQShxP8W6cqv?))st6X#lvt?EOO_DnNo=7qfxOKwEF9%QK> za?J!pZcKk%Rh4rVAwqplfLC9TL8m_J86B-c@RHiu*+voHwQ0oj6KnZC+S9@iJb5<> zUxR&MY3KYSTxnzqjgI^d(+mP7X!eU#QrrWXa0;w{((ej zAvWKYj~+a*h(5Mbd#pe{HX8F^c%gh*^{$t>`Q(45|M$#4659hdzUuVWgymTy?@y>h zd2!$?|F<)H93DaDnW{tlK4pjHg?hB|H^PQ@BWCt?kQ`jEmuIcUwMl>xodJj|u*h%X z6w`>b+9PotU(Q?789!tL()fiG{p{;2vEjwTJ>*0>Z#$cF#~yfKXp^AU2&^L6E&%tQ zs+M|p`3>@N!zXO6|DVhr6&lq+5QcO?I9+HYHfp9k-Kl5d`!D}nqYKw)&xl~AM%Bv{ z+wr0^V=@WW{6Ekb3`NtF))5vUthVjQvmVAN%(H%uRBt}(I^?Mxp#cs z0Ltt0obSPQRe!nK+(7X0N=AdCN> zvj}SY=Lz<$aU_ouXE8dLIHOdrE^r1=c(HZaJ={BAz};MX{G5+N|LU~=uGxMXWNRJi z_h{?Gub}-ux5^(!WRiWBhG#i%+cZHDuJf6E{kw4O>3MMN*8KGR;w++^CCiB$HA&DD zQ~vmXCf8tUE#J=~eqf%%?x|INjlE7A^%huK4UdwT^LQ*%Gsd4@q`e7Z;ZH4s9RCK` ziNA*ct7pK z+W_|qKCFU|(*A4t2n4v~4k?%E`f>Pl=^xpOe*KxR6D|M0vY|ncJRtZk*cLjUa)WQ_ ztiXBMh#0&0sH~G2W8+ z{kEc*q9Sk-6u$7d1X`JCOu3X(d8j7!J8ha;jQu02m4~C{x``7woMIkV|GoINi-uln zzH=!1%0Jl|2x&~C5R3OTHXlzP^?Mv%3&#=@kqu92i#r`VST2FoXnoV6(NXf9KAMQN zuhr@P&=voycC3Mx zD<3>tf`_d(!JO=R@I6ISX@sA2VJ8M9A$Wx8V7^y$-bZ9l^v1 zxJJLkldzFBM5xAxgHYo9Y`Cc)T9hn9++3IG7m>ORnXM6g+el?o&$ zd|9V?YY2wY=YfSk03goyZxIE2R(VcvQXq5@T}?I9fSH~A zKm^lNW>5R$iHY3tr0A@y?xIk#NGb43i{#`e&uYdcY2QEHx^GmC@_d&J@>UJj`2Ful zzZE^=#~Y--mG6G8PVC!5PV*Hb)uRpWh7|*wdN~Y- zmNu7dPaNY~ciYZw8KF#C#*o)9x6Ip#E=95z7afjiQvgDfYwv>+*#V>8SY*n(a5En= zWJ`G!@7?VIaCA)FCa|d?j1f7)wx8d#rSq!cN(BJ;xNfxkail7ab+0$PGfC2$+CvK+M>0$b1J!gC%F^9`@dj&hbQ2 zClNP(aUFeFz-w|$> zqG8rQ*8-x-q_uH)8&>ZoAS@zWQVq$lnqjt$Iik$*YE<)zTIko%*rQ&W{$l7z6nk~K ziIf?9K#Q%#`jSRl=^>$yYMVwsE{zmHQ%&!%h6FbkZl6|Yyg7<79A^&8D`6|#cm^VRpxzZg ze5dU#D|xh;{Jp`Fo&xe{6OraZy%U3Q{QjSa{(mkUD{dO= z>_*X2f*B)Y>|MYkO)o~$MayD=@+8D-?HEc_s!nXy_mp#gW5-7(aINLjWeq??S_B|B zwU{aq-E8B9kdNBi<0WMPLgu1yDTo-GO7IqW=oEV-ec5~99x^@@>lF}y*Hip{bv2@B z2@Ch~eJOiOK!L=TOXsXbCg3qLmBrViL%*TitV64rdwnNNSX8TZl(8EFcXgmdNHs>I zl9sP0aeBig)!$0VJUw@Onyg3NWZo#q(ycN(8JC*A_LJtPtQn>=qW$j4;!I7&lSUt>aL@EchW%Di|rxY zXBkY>A;edcxbB`~3Py!%k%8L1Z}uMxhsSmo@!94=ZDxwm3#73=4sRJo%yXa~OYiM( zZX1)l1K&6b*t6cD51Omd`MUkIXaOTEotO+Na~CJ^bac zzBPy9<##AbrAM~4g_)XWbnPB5cSLH)Blhl-5=SQJSlD1ss4-~|190u(jED|A_kd*nk0uhl(o@2zw z_$T=d%R0rL)u#@r{o%oIruJ6J#PzbllO9p&q9RDc84|Mef<*_vbWc(IB`H(;?%{J5 zTkjViy89MQPD@Xo*Ru)AQUhH4N%xZJ;;JwGmT(dp1KKGyn$YMWXA-~#zFus)`DwTT zGR0Y8!Qt**gsZk|KfRQS_v)8kPh(G#x(k znEOH$)T+Eh0c*y=TP~Va9*|<^#*sNWWTS4&Nh%@A1%rkh$>b|6Ds}}< zCemr+zrKJw-FLhj_@~QqoU5v9l^}(w0K01*rHj6t_+%3(R9SEog*ZO2LyR*tREyhq zq$kYU*MP-_XaE5%9TC0MG!E^Gqsu)G!NF4OjgxkChWec%19gmBpH=QTbS+9kYxohv zPf-T2w<0^%-Y=T=9(4idcl`9bZM^rFMRX|exxsaUUx3$FX@-Jjfo$v^5LtVZZ@NcZ zsO;XXTNEir!M^sRNzZlYzXdT*4n0U~@iK2Buiq}xO&JQWV|=EeoPe4=RlVWI+~|oB zxwvvyn$<69c_%!IrS#$C%!jF@sUq;DXSgX-89USOFqG)_Mc-;ZOi*RdhRfe zm(QJr*jrNJMt=RfEdy%8H2*e>iQP~#bL_BB6896`1TNUUi5D3JeXaQNzJoPzR7PuH z$vx9Kq~eZ@-u>zANJR)(N2Nx7PK~Hjl6p9aSDS>`uSzIYb{E%Neb#yIP;~Rrqepvt zeD1Ga=klFngX2;)L!ZFmk%J1ddET!q3KFU@9<1!OdMTTSK^d8un%v|fN=4Dx5-Eiu z9x_MXFN&AnCiWGwZx7LQQp>)D++d92125G(Q(Qo!!)CTpA>%HERK*>Ai%A0HN+$58 zMc4dz^rKSKlWrk%5#@w2mu@6M6?ioro#H>8sozk4JTMzIZEZwe4OW5gO6obEH{1A$ zN=uX6N?MJ*Yh9oiUr}MyU-O7DPKb&&ao@MyR#C1i^&$q=_O>KRFhzh^LuEcSV3v*Q9l`O3+8QNmD z5;F&0r{^o*ThVBihSUVf{sR5TSJ=_nncq=dzssHj$|NN( z8Sla&ot?qNBX-|#rkYEFokIemmpHTYbD^QgS#6G!7yA5#o{ikVtHvV3$`mergTKcJ zt^Q_a_IoSEdSdnkalK9VTjkvkwG6W7l+gBOzRjJe6>Z?ceOn1+=LY4E!2Pu!<#XTa z4yCHh!f`E{)zS2(=4Sj_Fl^=fLBBT>G~+a#B~Lo4vmYPyt}(uMS}k}nI-rxs`f!o; z!pFABI^K(G!W*$vU}Wj5kXWxTR~r%G#O4Fa9_>dk_!)aOP=tj8*pmhQX5ioN{0Z9j z?5`QfR+O^mj_d~o9xOd?9{c%IkdtNw_9`oyH5pi|<`^U3hrP5&Nn-B&;D=HmjKTwE+cZ|!ochKIMWOdq(Kz$HHkejX!N zQbJ1;6EP6(Q=!$17N7bI9dW9P#7_NVM|5w}^vqT?Sg-vCc8hV(t&XHhPEk@tBkCz? zFrWo2>(%+NNyO1YaB&f$0fc2`ev{*g-F?y$khZdJ2aZ2G+lM|0UmLvlR$c@p{Cd(Q zC1S5;(r43*=IRR?Iiz1_?3{wLF`_x$Tlo;Sa#c#JsA$c4NX=bPE!f=I?HjJsW%ob} z3jMGG^EaVZ;(FLEqtSIr-d}TYd-IKQsM}9=F-SbnTaZ|F$ojfqR8u^7MpNth-XhGkBK33Vav|m z!r|1yuK~`(c|6J9ma_{!4;Wv^key1h(&#c~X{~97!wJ;P3eaPP!Gl!|{w2Dt-Fs#v(Ix z1?7l-T^Fb~GI@-QEmqCdRVoE^Pq@FOK>0Au0G6E0!oUAEntrg=-RO}`n(mOb1xDIy zt$=dqqBke1na3-RUXpa$*cqLzmt4Ym0PhMW+3+D>ph3wUPdtVM^$1V)(u zwn)N*?09=r_G;`*P-do0d7d?3MJt6e6tzg4Z!@-WZtOPaTCO4F_HCoBsKE&oz3m70 zeM;X6Q!E6?Q_}ylFzgcHVdJW1mZN|!G3ibnnOo>1Rsqw=m>e*O z(5?`Vzb}EYKMicP)ioMe_4_DR4)z%}v5HIUn_`u_D?5OV?f)e2YGG2RDpyvn7&zo+(*mwy%6~a# z*Yd*cQC;77ZEZx`SlIs1?4~KrQDwizXA7~q>Zh@x{ft?V#Es1z{Rc2wxQUeL0Po%2 zLKw`s&O%NB1Y!l%yVMWaLB46SXmm=hnLnbshx>6s#r&y@DCpQz#Oib94*fpJS; zs_`-=-9qz-(%Y4PnE~>_N1zbf0#Tolp@HOWrA4#i$wm0VFthzvAO$0@FcLRgEWFKlne8-Hn+!5i`B`9pNW~Lmhzc|#1CiC(#0E4Ef!-%`ZE30j88|TWEVC4kv*i7=l3p%!vbwB>0M2wOC%pE z34uz#dIZO9pGA`aa__bazwJuu?u?4VuW=|k_SV(bI-1DpRp^CX8GI_Tts}1x^W50N zWwm(W=C-1R*@|2+@!yJ`Dzj!x1oS3$nXhi*H$zS@Rvr$|)U4%mfi@C2jjXHB@s|oB zle@d$?<+R>RQEDIi@@*u%gxo*Q5PtVr%vT)Gh=sk%(=N{r;?=Mvs5b_rX>cs@-Ja| zViLk9&z6BHblsKL>GeK@CL`coT<>z^)b{tI9dG!^yGz*D5Xv6|1FFDC2++mVvy1RH z=y0yn@i-`M-!nx!&)WBEYipC|8BM|19qjCk(>X-M#q~Dk?gcTgJ#};Zj))7oPv`_= zSlsWts)W$+%6Gta%Iq7p{xh-nNIFK__<2_veKRjjU_k!o?MUR4rm)6^*mmwL7kSVXrKo<%HjGa9_6d z<(zL83(Zxhb0 zw*LI!h304GAq_@BSp0tGohM5O1*k1FWkmUu?Nf1aZ+5dCt5WR|&-l1#BMy0)o^HQ^ zNGk@RlSr0VD`~`RVQ)SR%JLm`hqOg@b;!)fAYh_G|BUi|>OVElV6!=6GTV*V{#1qi>qZ=$CNZ?b| z?)ZY<31h?Fo1a`+36S=DaZf~l7zF6#;-}_9`XehUZpah!mfLdistaJ8X-SfdVxEZq zh&a`w(qP^sErje83chA22tVsLw(Hy93urM$-NS$u9IGC0<2KXs+sj)JKQ+k!Zxx<< z1hzsFtItR`U`Qt1S$U;a6?!+@RzL3L$*J)-DGj^E;mbzCyn#>!V{6?f2RNA)+HQX0 zxaa&+Kb?~2#J-f_i72h-y5l~cUJGn9Ak4umOgY?JOzg$Pl}bUoEGI~$P8OMvLY^sS z8=KY2v)m;+MB`>evOaw`bYV%nd=O>6Glr+WIN|aT(3aC zHW6=BuD#B_c=33Z?fztAW$os;Zr~&#`FV4_tc=RyzETX8zWU?GcT?J~iCzlwiDEDS zo(11W8do;B_pmSakj*qC-SJL`= zp@l;|5e8~%>VM-HJ4x2`ul_WvJ*t{%VTa64e?L5tGy2AJp~Cl$wCkZS{VZW_4$##y K(yUasL;e>})_utU diff --git a/doc/en/img/pytest_logo_curves.svg b/doc/en/img/pytest_logo_curves.svg new file mode 100644 index 00000000000..e05ceb11233 --- /dev/null +++ b/doc/en/img/pytest_logo_curves.svg @@ -0,0 +1,29 @@ + + + + + From 534d174fd24f8066518e396fbdd559eb7cf7b5a9 Mon Sep 17 00:00:00 2001 From: Chris NeJame Date: Wed, 16 Dec 2020 11:53:14 -0500 Subject: [PATCH 0017/2772] Clarify fixture execution order and provide visual aids (#7381) Co-authored-by: Bruno Oliveira Co-authored-by: Ran Benita --- AUTHORS | 1 + .../example/fixtures/fixture_availability.svg | 132 ++ .../fixtures/fixture_availability_plugins.svg | 142 ++ .../example/fixtures/test_fixtures_order.py | 38 - .../fixtures/test_fixtures_order_autouse.py | 45 + .../fixtures/test_fixtures_order_autouse.svg | 64 + ..._fixtures_order_autouse_multiple_scopes.py | 31 + ...fixtures_order_autouse_multiple_scopes.svg | 76 + ...est_fixtures_order_autouse_temp_effects.py | 36 + ...st_fixtures_order_autouse_temp_effects.svg | 100 + .../test_fixtures_order_dependencies.py | 45 + .../test_fixtures_order_dependencies.svg | 60 + .../test_fixtures_order_dependencies_flat.svg | 51 + ...st_fixtures_order_dependencies_unclear.svg | 60 + .../fixtures/test_fixtures_order_scope.py | 36 + .../fixtures/test_fixtures_order_scope.svg | 55 + .../test_fixtures_request_different_scope.py | 29 + .../test_fixtures_request_different_scope.svg | 115 ++ doc/en/fixture.rst | 1751 +++++++++++++---- 19 files changed, 2413 insertions(+), 454 deletions(-) create mode 100644 doc/en/example/fixtures/fixture_availability.svg create mode 100644 doc/en/example/fixtures/fixture_availability_plugins.svg delete mode 100644 doc/en/example/fixtures/test_fixtures_order.py create mode 100644 doc/en/example/fixtures/test_fixtures_order_autouse.py create mode 100644 doc/en/example/fixtures/test_fixtures_order_autouse.svg create mode 100644 doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.py create mode 100644 doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.svg create mode 100644 doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.py create mode 100644 doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.svg create mode 100644 doc/en/example/fixtures/test_fixtures_order_dependencies.py create mode 100644 doc/en/example/fixtures/test_fixtures_order_dependencies.svg create mode 100644 doc/en/example/fixtures/test_fixtures_order_dependencies_flat.svg create mode 100644 doc/en/example/fixtures/test_fixtures_order_dependencies_unclear.svg create mode 100644 doc/en/example/fixtures/test_fixtures_order_scope.py create mode 100644 doc/en/example/fixtures/test_fixtures_order_scope.svg create mode 100644 doc/en/example/fixtures/test_fixtures_request_different_scope.py create mode 100644 doc/en/example/fixtures/test_fixtures_request_different_scope.svg diff --git a/AUTHORS b/AUTHORS index 72391122eb5..20798f3093d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -56,6 +56,7 @@ Charles Cloud Charles Machalow Charnjit SiNGH (CCSJ) Chris Lamb +Chris NeJame Christian Boelsen Christian Fetzer Christian Neumüller diff --git a/doc/en/example/fixtures/fixture_availability.svg b/doc/en/example/fixtures/fixture_availability.svg new file mode 100644 index 00000000000..3ca28447c45 --- /dev/null +++ b/doc/en/example/fixtures/fixture_availability.svg @@ -0,0 +1,132 @@ + + + + + + + + + + + tests + + + + + + + + + + subpackage + + + + + + + + + + test_subpackage.py + + + + + + + + + + + innermost + + test_order + + mid + + + + + + + 1 + + + + + + + 2 + + + + + + + 3 + + + + + + + + + test_top.py + + + + + + + + + innermost + + test_order + + + + + + + 1 + + + 2 + + + top + + order + diff --git a/doc/en/example/fixtures/fixture_availability_plugins.svg b/doc/en/example/fixtures/fixture_availability_plugins.svg new file mode 100644 index 00000000000..88e32d90809 --- /dev/null +++ b/doc/en/example/fixtures/fixture_availability_plugins.svg @@ -0,0 +1,142 @@ + + + + + + + + + + + plugin_a + + + + + + + + 4 + + + + + + + + + plugin_b + + + + + + + + 4 + + + + + + + + + tests + + + + + + + + 3 + + + + + + + + + subpackage + + + + + + + + 2 + + + + + + + + + test_subpackage.py + + + + + + + + 1 + + + + + + + + + + + + + inner + + test_order + + mid + + order + + + a_fix + + b_fix + diff --git a/doc/en/example/fixtures/test_fixtures_order.py b/doc/en/example/fixtures/test_fixtures_order.py deleted file mode 100644 index 97b3e80052b..00000000000 --- a/doc/en/example/fixtures/test_fixtures_order.py +++ /dev/null @@ -1,38 +0,0 @@ -import pytest - -# fixtures documentation order example -order = [] - - -@pytest.fixture(scope="session") -def s1(): - order.append("s1") - - -@pytest.fixture(scope="module") -def m1(): - order.append("m1") - - -@pytest.fixture -def f1(f3): - order.append("f1") - - -@pytest.fixture -def f3(): - order.append("f3") - - -@pytest.fixture(autouse=True) -def a1(): - order.append("a1") - - -@pytest.fixture -def f2(): - order.append("f2") - - -def test_order(f1, m1, f2, s1): - assert order == ["s1", "m1", "a1", "f3", "f1", "f2"] diff --git a/doc/en/example/fixtures/test_fixtures_order_autouse.py b/doc/en/example/fixtures/test_fixtures_order_autouse.py new file mode 100644 index 00000000000..ec282ab4b2b --- /dev/null +++ b/doc/en/example/fixtures/test_fixtures_order_autouse.py @@ -0,0 +1,45 @@ +import pytest + + +@pytest.fixture +def order(): + return [] + + +@pytest.fixture +def a(order): + order.append("a") + + +@pytest.fixture +def b(a, order): + order.append("b") + + +@pytest.fixture(autouse=True) +def c(b, order): + order.append("c") + + +@pytest.fixture +def d(b, order): + order.append("d") + + +@pytest.fixture +def e(d, order): + order.append("e") + + +@pytest.fixture +def f(e, order): + order.append("f") + + +@pytest.fixture +def g(f, c, order): + order.append("g") + + +def test_order_and_g(g, order): + assert order == ["a", "b", "c", "d", "e", "f", "g"] diff --git a/doc/en/example/fixtures/test_fixtures_order_autouse.svg b/doc/en/example/fixtures/test_fixtures_order_autouse.svg new file mode 100644 index 00000000000..36362e4fb00 --- /dev/null +++ b/doc/en/example/fixtures/test_fixtures_order_autouse.svg @@ -0,0 +1,64 @@ + + + + + + + + + + + + + autouse + + order + + a + + b + + c + + d + + e + + f + + g + + test_order + diff --git a/doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.py b/doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.py new file mode 100644 index 00000000000..de0c2642793 --- /dev/null +++ b/doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.py @@ -0,0 +1,31 @@ +import pytest + + +@pytest.fixture(scope="class") +def order(): + return [] + + +@pytest.fixture(scope="class", autouse=True) +def c1(order): + order.append("c1") + + +@pytest.fixture(scope="class") +def c2(order): + order.append("c2") + + +@pytest.fixture(scope="class") +def c3(order, c1): + order.append("c3") + + +class TestClassWithC1Request: + def test_order(self, order, c1, c3): + assert order == ["c1", "c3"] + + +class TestClassWithoutC1Request: + def test_order(self, order, c2): + assert order == ["c1", "c2"] diff --git a/doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.svg b/doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.svg new file mode 100644 index 00000000000..9f2180fe548 --- /dev/null +++ b/doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.svg @@ -0,0 +1,76 @@ + + + + + + + + order + + c1 + + c3 + + test_order + + + + + + TestWithC1Request + + + + + + + order + + c1 + + c2 + + test_order + + + + + + TestWithoutC1Request + + + + + autouse + diff --git a/doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.py b/doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.py new file mode 100644 index 00000000000..ba01ad32f57 --- /dev/null +++ b/doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.py @@ -0,0 +1,36 @@ +import pytest + + +@pytest.fixture +def order(): + return [] + + +@pytest.fixture +def c1(order): + order.append("c1") + + +@pytest.fixture +def c2(order): + order.append("c2") + + +class TestClassWithAutouse: + @pytest.fixture(autouse=True) + def c3(self, order, c2): + order.append("c3") + + def test_req(self, order, c1): + assert order == ["c2", "c3", "c1"] + + def test_no_req(self, order): + assert order == ["c2", "c3"] + + +class TestClassWithoutAutouse: + def test_req(self, order, c1): + assert order == ["c1"] + + def test_no_req(self, order): + assert order == [] diff --git a/doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.svg b/doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.svg new file mode 100644 index 00000000000..ac62ae46b40 --- /dev/null +++ b/doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.svg @@ -0,0 +1,100 @@ + + + + + + + + + + + TestWithAutouse + + + + + + + + + order + + c2 + + c3 + + c1 + + test_req + + + + + order + + c2 + + c3 + + test_no_req + + + autouse + + + + + + + + + TestWithoutAutouse + + + + + + order + + c1 + + test_req + + + + + order + + test_no_req + diff --git a/doc/en/example/fixtures/test_fixtures_order_dependencies.py b/doc/en/example/fixtures/test_fixtures_order_dependencies.py new file mode 100644 index 00000000000..b3512c2a64d --- /dev/null +++ b/doc/en/example/fixtures/test_fixtures_order_dependencies.py @@ -0,0 +1,45 @@ +import pytest + + +@pytest.fixture +def order(): + return [] + + +@pytest.fixture +def a(order): + order.append("a") + + +@pytest.fixture +def b(a, order): + order.append("b") + + +@pytest.fixture +def c(a, b, order): + order.append("c") + + +@pytest.fixture +def d(c, b, order): + order.append("d") + + +@pytest.fixture +def e(d, b, order): + order.append("e") + + +@pytest.fixture +def f(e, order): + order.append("f") + + +@pytest.fixture +def g(f, c, order): + order.append("g") + + +def test_order(g, order): + assert order == ["a", "b", "c", "d", "e", "f", "g"] diff --git a/doc/en/example/fixtures/test_fixtures_order_dependencies.svg b/doc/en/example/fixtures/test_fixtures_order_dependencies.svg new file mode 100644 index 00000000000..24418e63c9d --- /dev/null +++ b/doc/en/example/fixtures/test_fixtures_order_dependencies.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + order + + a + + b + + c + + d + + e + + f + + g + + test_order + diff --git a/doc/en/example/fixtures/test_fixtures_order_dependencies_flat.svg b/doc/en/example/fixtures/test_fixtures_order_dependencies_flat.svg new file mode 100644 index 00000000000..bbe7ad28339 --- /dev/null +++ b/doc/en/example/fixtures/test_fixtures_order_dependencies_flat.svg @@ -0,0 +1,51 @@ + + + + + order + + a + + b + + c + + d + + e + + f + + g + + test_order + diff --git a/doc/en/example/fixtures/test_fixtures_order_dependencies_unclear.svg b/doc/en/example/fixtures/test_fixtures_order_dependencies_unclear.svg new file mode 100644 index 00000000000..150724f80a3 --- /dev/null +++ b/doc/en/example/fixtures/test_fixtures_order_dependencies_unclear.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + order + + a + + b + + c + + d + + e + + f + + g + + test_order + diff --git a/doc/en/example/fixtures/test_fixtures_order_scope.py b/doc/en/example/fixtures/test_fixtures_order_scope.py new file mode 100644 index 00000000000..5d9487cab34 --- /dev/null +++ b/doc/en/example/fixtures/test_fixtures_order_scope.py @@ -0,0 +1,36 @@ +import pytest + + +@pytest.fixture(scope="session") +def order(): + return [] + + +@pytest.fixture +def func(order): + order.append("function") + + +@pytest.fixture(scope="class") +def cls(order): + order.append("class") + + +@pytest.fixture(scope="module") +def mod(order): + order.append("module") + + +@pytest.fixture(scope="package") +def pack(order): + order.append("package") + + +@pytest.fixture(scope="session") +def sess(order): + order.append("session") + + +class TestClass: + def test_order(self, func, cls, mod, pack, sess, order): + assert order == ["session", "package", "module", "class", "function"] diff --git a/doc/en/example/fixtures/test_fixtures_order_scope.svg b/doc/en/example/fixtures/test_fixtures_order_scope.svg new file mode 100644 index 00000000000..ebaf7e4e245 --- /dev/null +++ b/doc/en/example/fixtures/test_fixtures_order_scope.svg @@ -0,0 +1,55 @@ + + + + + + + order + + sess + + pack + + mod + + cls + + func + + test_order + + + + + + TestClass + + diff --git a/doc/en/example/fixtures/test_fixtures_request_different_scope.py b/doc/en/example/fixtures/test_fixtures_request_different_scope.py new file mode 100644 index 00000000000..00e2e46d845 --- /dev/null +++ b/doc/en/example/fixtures/test_fixtures_request_different_scope.py @@ -0,0 +1,29 @@ +import pytest + + +@pytest.fixture +def order(): + return [] + + +@pytest.fixture +def outer(order, inner): + order.append("outer") + + +class TestOne: + @pytest.fixture + def inner(self, order): + order.append("one") + + def test_order(self, order, outer): + assert order == ["one", "outer"] + + +class TestTwo: + @pytest.fixture + def inner(self, order): + order.append("two") + + def test_order(self, order, outer): + assert order == ["two", "outer"] diff --git a/doc/en/example/fixtures/test_fixtures_request_different_scope.svg b/doc/en/example/fixtures/test_fixtures_request_different_scope.svg new file mode 100644 index 00000000000..ad98469ced0 --- /dev/null +++ b/doc/en/example/fixtures/test_fixtures_request_different_scope.svg @@ -0,0 +1,115 @@ + + + + + + + + + + test_fixtures_request_different_scope.py + + + + + + + + + + + + inner + + test_order + + + + + + TestOne + + + + + + + + 1 + + + + + + + 2 + + + + + + + + + + + inner + + test_order + + + + + + TestTwo + + + + + + + + 1 + + + 2 + + + outer + + order + diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index 963fc32e6b0..c74984563ab 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -12,6 +12,8 @@ pytest fixtures: explicit, modular, scalable .. _`xUnit`: https://en.wikipedia.org/wiki/XUnit .. _`Software test fixtures`: https://en.wikipedia.org/wiki/Test_fixture#Software .. _`Dependency injection`: https://en.wikipedia.org/wiki/Dependency_injection +.. _`Transaction`: https://en.wikipedia.org/wiki/Transaction_processing +.. _`linearizable`: https://en.wikipedia.org/wiki/Linearizability `Software test fixtures`_ initialize test functions. They provide a fixed baseline so that tests execute reliably and produce consistent, @@ -35,6 +37,10 @@ style of setup/teardown functions: to configuration and component options, or to re-use fixtures across function, class, module or whole test session scopes. +* teardown logic can be easily, and safely managed, no matter how many fixtures + are used, without the need to carefully handle errors by hand or micromanage + the order that cleanup steps are added. + In addition, pytest continues to support :ref:`xunitsetup`. You can mix both styles, moving incrementally from classic to new style, as you prefer. You can also start out from existing :ref:`unittest.TestCase @@ -115,32 +121,529 @@ for reference: .. _`@pytest.fixture`: .. _`pytest.fixture`: -Fixtures as Function arguments ------------------------------------------ +What fixtures are +----------------- + +Before we dive into what fixtures are, let's first look at what a test is. + +In the simplest terms, a test is meant to look at the result of a particular +behavior, and make sure that result aligns with what you would expect. +Behavior is not something that can be empirically measured, which is why writing +tests can be challenging. + +"Behavior" is the way in which some system **acts in response** to a particular +situation and/or stimuli. But exactly *how* or *why* something is done is not +quite as important as *what* was done. + +You can think of a test as being broken down into four steps: + +1. **Arrange** +2. **Act** +3. **Assert** +4. **Cleanup** + +**Arrange** is where we prepare everything for our test. This means pretty +much everything except for the "**act**". It's lining up the dominoes so that +the **act** can do its thing in one, state-changing step. This can mean +preparing objects, starting/killing services, entering records into a database, +or even things like defining a URL to query, generating some credentials for a +user that doesn't exist yet, or just waiting for some process to finish. + +**Act** is the singular, state-changing action that kicks off the **behavior** +we want to test. This behavior is what carries out the changing of the state of +the system under test (SUT), and it's the resulting changed state that we can +look at to make a judgement about the behavior. This typically takes the form of +a function/method call. + +**Assert** is where we look at that resulting state and check if it looks how +we'd expect after the dust has settled. It's where we gather evidence to say the +behavior does or does not aligns with what we expect. The ``assert`` in our test +is where we take that measurement/observation and apply our judgement to it. If +something should be green, we'd say ``assert thing == "green"``. + +**Cleanup** is where the test picks up after itself, so other tests aren't being +accidentally influenced by it. + +At it's core, the test is ultimately the **act** and **assert** steps, with the +**arrange** step only providing the context. **Behavior** exists between **act** +and **assert**. + +Back to fixtures +^^^^^^^^^^^^^^^^ + +"Fixtures", in the literal sense, are each of the **arrange** steps and data. They're +everything that test needs to do its thing. + +At a basic level, test functions request fixtures by declaring them as +arguments, as in the ``test_ehlo(smtp_connection):`` in the previous example. -Test functions can receive fixture objects by naming them as an input -argument. For each argument name, a fixture function with that name provides -the fixture object. Fixture functions are registered by marking them with -:py:func:`@pytest.fixture `. Let's look at a simple -self-contained test module containing a fixture and a test function -using it: +In pytest, "fixtures" are functions you define that serve this purpose. But they +don't have to be limited to just the **arrange** steps. They can provide the +**act** step, as well, and this can be a powerful technique for designing more +complex tests, especially given how pytest's fixture system works. But we'll get +into that further down. + +We can tell pytest that a particular function is a fixture by decorating it with +:py:func:`@pytest.fixture `. Here's a simple example of +what a fixture in pytest might look like: .. code-block:: python - # content of ./test_smtpsimple.py import pytest + class Fruit: + def __init__(self, name): + self.name = name + + def __eq__(self, other): + return self.name == other.name + + @pytest.fixture - def smtp_connection(): - import smtplib + def my_fruit(): + return Fruit("apple") + + + @pytest.fixture + def fruit_basket(my_fruit): + return [Fruit("banana"), my_fruit] + + + def test_my_fruit_in_basket(my_fruit, fruit_basket): + assert my_fruit in fruit_basket + + + +Tests don't have to be limited to a single fixture, either. They can depend on +as many fixtures as you want, and fixtures can use other fixtures, as well. This +is where pytest's fixture system really shines. + +Don't be afraid to break things up if it makes things cleaner. + +"Requesting" fixtures +--------------------- + +So fixtures are how we *prepare* for a test, but how do we tell pytest what +tests and fixtures need which fixtures? + +At a basic level, test functions request fixtures by declaring them as +arguments, as in the ``test_my_fruit_in_basket(my_fruit, fruit_basket):`` in the +previous example. + +At a basic level, pytest depends on a test to tell it what fixtures it needs, so +we have to build that information into the test itself. We have to make the test +"**request**" the fixtures it depends on, and to do this, we have to +list those fixtures as parameters in the test function's "signature" (which is +the ``def test_something(blah, stuff, more):`` line). + +When pytest goes to run a test, it looks at the parameters in that test +function's signature, and then searches for fixtures that have the same names as +those parameters. Once pytest finds them, it runs those fixtures, captures what +they returned (if anything), and passes those objects into the test function as +arguments. + +Quick example +^^^^^^^^^^^^^ + +.. code-block:: python + + import pytest + + + class Fruit: + def __init__(self, name): + self.name = name + self.cubed = False + + def cube(self): + self.cubed = True + + + class FruitSalad: + def __init__(self, *fruit_bowl): + self.fruit = fruit_bowl + self._cube_fruit() + + def _cube_fruit(self): + for fruit in self.fruit: + fruit.cube() + + + # Arrange + @pytest.fixture + def fruit_bowl(): + return [Fruit("apple"), Fruit("banana")] + + + def test_fruit_salad(fruit_bowl): + # Act + fruit_salad = FruitSalad(*fruit_bowl) + + # Assert + assert all(fruit.cubed for fruit in fruit_salad.fruit) + +In this example, ``test_fruit_salad`` "**requests**" ``fruit_bowl`` (i.e. +``def test_fruit_salad(fruit_bowl):``), and when pytest sees this, it will +execute the ``fruit_bowl`` fixture function and pass the object it returns into +``test_fruit_salad`` as the ``fruit_bowl`` argument. + +Here's roughly +what's happening if we were to do it by hand: + +.. code-block:: python + + def fruit_bowl(): + return [Fruit("apple"), Fruit("banana")] + + + def test_fruit_salad(fruit_bowl): + # Act + fruit_salad = FruitSalad(*fruit_bowl) + + # Assert + assert all(fruit.cubed for fruit in fruit_salad.fruit) + + + # Arrange + bowl = fruit_bowl() + test_fruit_salad(fruit_bowl=bowl) + +Fixtures can **request** other fixtures +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +One of pytest's greatest strengths is its extremely flexible fixture system. It +allows us to boil down complex requirements for tests into more simple and +organized functions, where we only need to have each one describe the things +they are dependent on. We'll get more into this further down, but for now, +here's a quick example to demonstrate how fixtures can use other fixtures: + +.. code-block:: python + + # contents of test_append.py + import pytest + + + # Arrange + @pytest.fixture + def first_entry(): + return "a" + + + # Arrange + @pytest.fixture + def order(first_entry): + return [first_entry] + + + def test_string(order): + # Act + order.append("b") + + # Assert + assert order == ["a", "b"] + + +Notice that this is the same example from above, but very little changed. The +fixtures in pytest **request** fixtures just like tests. All the same +**requesting** rules apply to fixtures that do for tests. Here's how this +example would work if we did it by hand: + +.. code-block:: python + + def first_entry(): + return "a" + + + def order(first_entry): + return [first_entry] + + + def test_string(order): + # Act + order.append("b") + + # Assert + assert order == ["a", "b"] + + + entry = first_entry() + the_list = order(first_entry=entry) + test_string(order=the_list) + +Fixtures are reusable +^^^^^^^^^^^^^^^^^^^^^ + +One of the things that makes pytest's fixture system so powerful, is that it +gives us the abilty to define a generic setup step that can reused over and +over, just like a normal function would be used. Two different tests can request +the same fixture and have pytest give each test their own result from that +fixture. + +This is extremely useful for making sure tests aren't affected by each other. We +can use this system to make sure each test gets its own fresh batch of data and +is starting from a clean state so it can provide consistent, repeatable results. + +Here's an example of how this can come in handy: + +.. code-block:: python + + # contents of test_append.py + import pytest + + + # Arrange + @pytest.fixture + def first_entry(): + return "a" + + + # Arrange + @pytest.fixture + def order(first_entry): + return [first_entry] + + + def test_string(order): + # Act + order.append("b") + + # Assert + assert order == ["a", "b"] + + + def test_int(order): + # Act + order.append(2) + + # Assert + assert order == ["a", 2] + + +Each test here is being given its own copy of that ``list`` object, +which means the ``order`` fixture is getting executed twice (the same +is true for the ``first_entry`` fixture). If we were to do this by hand as +well, it would look something like this: + +.. code-block:: python + + def first_entry(): + return "a" + + + def order(first_entry): + return [first_entry] + + def test_string(order): + # Act + order.append("b") + + # Assert + assert order == ["a", "b"] + + + def test_int(order): + # Act + order.append(2) + + # Assert + assert order == ["a", 2] + + + entry = first_entry() + the_list = order(first_entry=entry) + test_string(order=the_list) + + entry = first_entry() + the_list = order(first_entry=entry) + test_int(order=the_list) + +A test/fixture can **request** more than one fixture at a time +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Tests and fixtures aren't limited to **requesting** a single fixture at a time. +They can request as many as they like. Here's another quick example to +demonstrate: + +.. code-block:: python + + # contents of test_append.py + import pytest + + + # Arrange + @pytest.fixture + def first_entry(): + return "a" + + + # Arrange + @pytest.fixture + def second_entry(): + return 2 + + + # Arrange + @pytest.fixture + def order(first_entry, second_entry): + return [first_entry, second_entry] + + + # Arrange + @pytest.fixture + def expected_list(): + return ["a", 2, 3.0] + + + def test_string(order, expected_list): + # Act + order.append(3.0) + + # Assert + assert order == expected_list + +Fixtures can be **requested** more than once per test (return values are cached) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Fixtures can also be **requested** more than once during the same test, and +pytest won't execute them again for that test. This means we can **request** +fixtures in multiple fixtures that are dependent on them (and even again in the +test itself) without those fixtures being executed more than once. + +.. code-block:: python + + # contents of test_append.py + import pytest + + + # Arrange + @pytest.fixture + def first_entry(): + return "a" + + + # Arrange + @pytest.fixture + def order(): + return [] + + + # Act + @pytest.fixture + def append_first(order, first_entry): + return order.append(first_entry) + + + def test_string_only(append_first, order, first_entry): + # Assert + assert order == [first_entry] + +If a **requested** fixture was executed once for every time it was **requested** +during a test, then this test would fail because both ``append_first`` and +``test_string_only`` would see ``order`` as an empty list (i.e. ``[]``), but +since the return value of ``order`` was cached (along with any side effects +executing it may have had) after the first time it was called, both the test and +``append_first`` were referencing the same object, and the test saw the effect +``append_first`` had on that object. + +.. _`autouse`: +.. _`autouse fixtures`: + +Autouse fixtures (fixtures you don't have to request) +----------------------------------------------------- + +Sometimes you may want to have a fixture (or even several) that you know all +your tests will depend on. "Autouse" fixtures are a convenient way to make all +tests automatically **request** them. This can cut out a +lot of redundant **requests**, and can even provide more advanced fixture usage +(more on that further down). + +We can make a fixture an autouse fixture by passing in ``autouse=True`` to the +fixture's decorator. Here's a simple example for how they can be used: + +.. code-block:: python + + # contents of test_append.py + import pytest + + + @pytest.fixture + def first_entry(): + return "a" + + + @pytest.fixture + def order(first_entry): + return [] + + + @pytest.fixture(autouse=True) + def append_first(order, first_entry): + return order.append(first_entry) + + + def test_string_only(order, first_entry): + assert order == [first_entry] + + + def test_string_and_int(order, first_entry): + order.append(2) + assert order == [first_entry, 2] + +In this example, the ``append_first`` fixture is an autouse fixture. Because it +happens automatically, both tests are affected by it, even though neither test +**requested** it. That doesn't mean they *can't* be **requested** though; just +that it isn't *necessary*. + +.. _smtpshared: + +Scope: sharing fixtures across classes, modules, packages or session +-------------------------------------------------------------------- + +.. regendoc:wipe + +Fixtures requiring network access depend on connectivity and are +usually time-expensive to create. Extending the previous example, we +can add a ``scope="module"`` parameter to the +:py:func:`@pytest.fixture ` invocation +to cause a ``smtp_connection`` fixture function, responsible to create a connection to a preexisting SMTP server, to only be invoked +once per test *module* (the default is to invoke once per test *function*). +Multiple test functions in a test module will thus +each receive the same ``smtp_connection`` fixture instance, thus saving time. +Possible values for ``scope`` are: ``function``, ``class``, ``module``, ``package`` or ``session``. + +The next example puts the fixture function into a separate ``conftest.py`` file +so that tests from multiple test modules in the directory can +access the fixture function: + +.. code-block:: python + + # content of conftest.py + import pytest + import smtplib + + + @pytest.fixture(scope="module") + def smtp_connection(): return smtplib.SMTP("smtp.gmail.com", 587, timeout=5) +.. code-block:: python + + # content of test_module.py + + def test_ehlo(smtp_connection): response, msg = smtp_connection.ehlo() assert response == 250 + assert b"smtp.gmail.com" in msg + assert 0 # for demo purposes + + + def test_noop(smtp_connection): + response, msg = smtp_connection.noop() + assert response == 250 assert 0 # for demo purposes Here, the ``test_ehlo`` needs the ``smtp_connection`` fixture value. pytest @@ -149,442 +652,967 @@ marked ``smtp_connection`` fixture function. Running the test looks like this: .. code-block:: pytest - $ pytest test_smtpsimple.py + $ pytest test_module.py =========================== test session starts ============================ platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR - collected 1 item + collected 2 items + + test_module.py FF [100%] + + ================================= FAILURES ================================= + ________________________________ test_ehlo _________________________________ + + smtp_connection = + + def test_ehlo(smtp_connection): + response, msg = smtp_connection.ehlo() + assert response == 250 + assert b"smtp.gmail.com" in msg + > assert 0 # for demo purposes + E assert 0 + + test_module.py:7: AssertionError + ________________________________ test_noop _________________________________ + + smtp_connection = + + def test_noop(smtp_connection): + response, msg = smtp_connection.noop() + assert response == 250 + > assert 0 # for demo purposes + E assert 0 + + test_module.py:13: AssertionError + ========================= short test summary info ========================== + FAILED test_module.py::test_ehlo - assert 0 + FAILED test_module.py::test_noop - assert 0 + ============================ 2 failed in 0.12s ============================= + +You see the two ``assert 0`` failing and more importantly you can also see +that the **exactly same** ``smtp_connection`` object was passed into the +two test functions because pytest shows the incoming argument values in the +traceback. As a result, the two test functions using ``smtp_connection`` run +as quick as a single one because they reuse the same instance. + +If you decide that you rather want to have a session-scoped ``smtp_connection`` +instance, you can simply declare it: + +.. code-block:: python + + @pytest.fixture(scope="session") + def smtp_connection(): + # the returned fixture value will be shared for + # all tests requesting it + ... + + +Fixture scopes +^^^^^^^^^^^^^^ + +Fixtures are created when first requested by a test, and are destroyed based on their ``scope``: + +* ``function``: the default scope, the fixture is destroyed at the end of the test. +* ``class``: the fixture is destroyed during teardown of the last test in the class. +* ``module``: the fixture is destroyed during teardown of the last test in the module. +* ``package``: the fixture is destroyed during teardown of the last test in the package. +* ``session``: the fixture is destroyed at the end of the test session. + +.. note:: + + Pytest only caches one instance of a fixture at a time, which + means that when using a parametrized fixture, pytest may invoke a fixture more than once in + the given scope. + +.. _dynamic scope: + +Dynamic scope +^^^^^^^^^^^^^ + +.. versionadded:: 5.2 + +In some cases, you might want to change the scope of the fixture without changing the code. +To do that, pass a callable to ``scope``. The callable must return a string with a valid scope +and will be executed only once - during the fixture definition. It will be called with two +keyword arguments - ``fixture_name`` as a string and ``config`` with a configuration object. + +This can be especially useful when dealing with fixtures that need time for setup, like spawning +a docker container. You can use the command-line argument to control the scope of the spawned +containers for different environments. See the example below. + +.. code-block:: python + + def determine_scope(fixture_name, config): + if config.getoption("--keep-containers", None): + return "session" + return "function" + + + @pytest.fixture(scope=determine_scope) + def docker_container(): + yield spawn_container() + +Fixture errors +-------------- + +pytest does its best to put all the fixtures for a given test in a linear order +so that it can see which fixture happens first, second, third, and so on. If an +earlier fixture has a problem, though, and raises an exception, pytest will stop +executing fixtures for that test and mark the test as having an error. + +When a test is marked as having an error, it doesn't mean the test failed, +though. It just means the test couldn't even be attempted because one of the +things it depends on had a problem. + +This is one reason why it's a good idea to cut out as many unnecessary +dependencies as possible for a given test. That way a problem in something +unrelated isn't causing us to have an incomplete picture of what may or may not +have issues. + +Here's a quick example to help explain: + +.. code-block:: python + + import pytest + + + @pytest.fixture + def order(): + return [] + + + @pytest.fixture + def append_first(order): + order.append(1) + + + @pytest.fixture + def append_second(order, append_first): + order.extend([2]) + + + @pytest.fixture(autouse=True) + def append_third(order, append_second): + order += [3] + + + def test_order(order): + assert order == [1, 2, 3] + + +If, for whatever reason, ``order.append(1)`` had a bug and it raises an exception, +we wouldn't be able to know if ``order.extend([2])`` or ``order += [3]`` would +also have problems. After ``append_first`` throws an exception, pytest won't run +any more fixtures for ``test_order``, and it won't even try to run +``test_order`` itself. The only things that would've run would be ``order`` and +``append_first``. + + + + +.. _`finalization`: - test_smtpsimple.py F [100%] +Teardown/Cleanup (AKA Fixture finalization) +------------------------------------------- + +When we run our tests, we'll want to make sure they clean up after themselves so +they don't mess with any other tests (and also so that we don't leave behind a +mountain of test data to bloat the system). Fixtures in pytest offer a very +useful teardown system, which allows us to define the specific steps necessary +for each fixture to clean up after itself. + +This system can be leveraged in two ways. + +.. _`yield fixtures`: + +1. ``yield`` fixtures (recommended) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +"Yield" fixtures ``yield`` instead of ``return``. With these +fixtures, we can run some code and pass an object back to the requesting +fixture/test, just like with the other fixtures. The only differences are: + +1. ``return`` is swapped out for ``yield``. +2. Any teardown code for that fixture is placed *after* the ``yield``. + +Once pytest figures out a linear order for the fixtures, it will run each one up +until it returns or yields, and then move on to the next fixture in the list to +do the same thing. + +Once the test is finished, pytest will go back down the list of fixtures, but in +the *reverse order*, taking each one that yielded, and running the code inside +it that was *after* the ``yield`` statement. + +As a simple example, let's say we want to test sending email from one user to +another. We'll have to first make each user, then send the email from one user +to the other, and finally assert that the other user received that message in +their inbox. If we want to clean up after the test runs, we'll likely have to +make sure the other user's mailbox is emptied before deleting that user, +otherwise the system may complain. + +Here's what that might look like: + +.. code-block:: python + + import pytest + + from emaillib import Email, MailAdminClient + + + @pytest.fixture + def mail_admin(): + return MailAdminClient() + + + @pytest.fixture + def sending_user(mail_admin): + user = mail_admin.create_user() + yield user + admin_client.delete_user(user) + + + @pytest.fixture + def receiving_user(mail_admin): + user = mail_admin.create_user() + yield user + admin_client.delete_user(user) + + + def test_email_received(receiving_user, email): + email = Email(subject="Hey!", body="How's it going?") + sending_user.send_email(_email, receiving_user) + assert email in receiving_user.inbox + +Because ``receiving_user`` is the last fixture to run during setup, it's the first to run +during teardown. + +There is a risk that even having the order right on the teardown side of things +doesn't guarantee a safe cleanup. That's covered in a bit more detail in +:ref:`safe teardowns`. + +Handling errors for yield fixture +""""""""""""""""""""""""""""""""" + +If a yield fixture raises an exception before yielding, pytest won't try to run +the teardown code after that yield fixture's ``yield`` statement. But, for every +fixture that has already run successfully for that test, pytest will still +attempt to tear them down as it normally would. + +2. Adding finalizers directly +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +While yield fixtures are considered to be the cleaner and more straighforward +option, there is another choice, and that is to add "finalizer" functions +directly to the test's `request-context`_ object. It brings a similar result as +yield fixtures, but requires a bit more verbosity. + +In order to use this approach, we have to request the `request-context`_ object +(just like we would request another fixture) in the fixture we need to add +teardown code for, and then pass a callable, containing that teardown code, to +its ``addfinalizer`` method. + +We have to be careful though, because pytest will run that finalizer once it's +been added, even if that fixture raises an exception after adding the finalizer. +So to make sure we don't run the finalizer code when we wouldn't need to, we +would only add the finalizer once the fixture would have done something that +we'd need to teardown. + +Here's how the previous example would look using the ``addfinalizer`` method: + +.. code-block:: python + + import pytest + + from emaillib import Email, MailAdminClient + + + @pytest.fixture + def mail_admin(): + return MailAdminClient() + + + @pytest.fixture + def sending_user(mail_admin): + user = mail_admin.create_user() + yield user + admin_client.delete_user(user) + + + @pytest.fixture + def receiving_user(mail_admin, request): + user = mail_admin.create_user() + + def delete_user(): + admin_client.delete_user(user) + + request.addfinalizer(delete_user) + return user + + + @pytest.fixture + def email(sending_user, receiving_user, request): + _email = Email(subject="Hey!", body="How's it going?") + sending_user.send_email(_email, receiving_user) + + def empty_mailbox(): + receiving_user.delete_email(_email) + + request.addfinalizer(empty_mailbox) + return _email + + + def test_email_received(receiving_user, email): + assert email in receiving_user.inbox + + +It's a bit longer than yield fixtures and a bit more complex, but it +does offer some nuances for when you're in a pinch. + +.. _`safe teardowns`: + +Safe teardowns +-------------- + +The fixture system of pytest is *very* powerful, but it's still being run by a +computer, so it isn't able to figure out how to safely teardown everything we +throw at it. If we aren't careful, an error in the wrong spot might leave stuff +from our tests behind, and that can cause further issues pretty quickly. + +For example, consider the following tests (based off of the mail example from +above): + +.. code-block:: python + + import pytest + + from emaillib import Email, MailAdminClient + + + @pytest.fixture + def setup(): + mail_admin = MailAdminClient() + sending_user = mail_admin.create_user() + receiving_user = mail_admin.create_user() + email = Email(subject="Hey!", body="How's it going?") + sending_user.send_emai(email, receiving_user) + yield receiving_user, email + receiving_user.delete_email(email) + admin_client.delete_user(sending_user) + admin_client.delete_user(receiving_user) + + + def test_email_received(setup): + receiving_user, email = setup + assert email in receiving_user.inbox + +This version is a lot more compact, but it's also harder to read, doesn't have a +very descriptive fixture name, and none of the fixtures can be reused easily. + +There's also a more serious issue, which is that if any of those steps in the +setup raise an exception, none of the teardown code will run. + +One option might be to go with the ``addfinalizer`` method instead of yield +fixtures, but that might get pretty complex and difficult to maintain (and it +wouldn't be compact anymore). + +.. _`safe fixture structure`: + +Safe fixture structure +^^^^^^^^^^^^^^^^^^^^^^ + +The safest and simplest fixture structure requires limiting fixtures to only +making one state-changing action each, and then bundling them together with +their teardown code, as :ref:`the email examples above ` showed. + +The chance that a state-changing operation can fail but still modify state is +neglibible, as most of these operations tend to be `transaction`_-based (at +least at the level of testing where state could be left behind). So if we make +sure that any successful state-changing action gets torn down by moving it to a +separate fixture function and separating it from other, potentially failing +state-changing actions, then our tests will stand the best chance at leaving the +test environment the way they found it. + +For an example, let's say we have a website with a login page, and we have +access to an admin API where we can generate users. For our test, we want to: + +1. Create a user through that admin API +2. Launch a browser using Selenium +3. Go to the login page of our site +4. Log in as the user we created +5. Assert that their name is in the header of the landing page + +We wouldn't want to leave that user in the system, nor would we want to leave +that browser session running, so we'll want to make sure the fixtures that +create those things clean up after themselves. + +Here's what that might look like: + +.. note:: + + For this example, certain fixtures (i.e. ``base_url`` and + ``admin_credentials``) are implied to exist elsewhere. So for now, let's + assume they exist, and we're just not looking at them. + +.. code-block:: python + + from uuid import uuid4 + from urllib.parse import urljoin + + from selenium.webdriver import Chrome + import pytest + + from src.utils.pages import LoginPage, LandingPage + from src.utils import AdminApiClient + from src.utils.data_types import User + + + @pytest.fixture + def admin_client(base_url, admin_credentials): + return AdminApiClient(base_url, **admin_credentials) + + + @pytest.fixture + def user(admin_client): + _user = User(name="Susan", username=f"testuser-{uuid4()}", password="P4$$word") + admin_client.create_user(_user) + yield _user + admin_client.delete_user(_user) + + + @pytest.fixture + def driver(): + _driver = Chrome() + yield _driver + _driver.quit() + + + @pytest.fixture + def login(driver, base_url, user): + driver.get(urljoin(base_url, "/login")) + page = LoginPage(driver) + page.login(user) + + + @pytest.fixture + def landing_page(driver, login): + return LandingPage(driver) + + + def test_name_on_landing_page_after_login(landing_page, user): + assert landing_page.header == f"Welcome, {user.name}!" + +The way the dependencies are laid out means it's unclear if the ``user`` fixture +would execute before the ``driver`` fixture. But that's ok, because those are +atomic operations, and so it doesn't matter which one runs first because the +sequence of events for the test is still `linearizable`_. But what *does* matter +is that, no matter which one runs first, if the one raises an exception while +the other would not have, neither will have left anything behind. If ``driver`` +executes before ``user``, and ``user`` raises an exception, the driver will +still quit, and the user was never made. And if ``driver`` was the one to raise +the exception, then the driver would never have been started and the user would +never have been made. + +.. note: + + While the ``user`` fixture doesn't *actually* need to happen before the + ``driver`` fixture, if we made ``driver`` request ``user``, it might save + some time in the event that making the user raises an exception, since it + won't bother trying to start the driver, which is a fairly expensive + operation. + +.. _`conftest.py`: +.. _`conftest`: - ================================= FAILURES ================================= - ________________________________ test_ehlo _________________________________ +Fixture availabiility +--------------------- - smtp_connection = +Fixture availability is determined from the perspective of the test. A fixture +is only available for tests to request if they are in the scope that fixture is +defined in. If a fixture is defined inside a class, it can only be requested by +tests inside that class. But if a fixture is defined inside the global scope of +the module, than every test in that module, even if it's defined inside a class, +can request it. - def test_ehlo(smtp_connection): - response, msg = smtp_connection.ehlo() - assert response == 250 - > assert 0 # for demo purposes - E assert 0 +Similarly, a test can also only be affected by an autouse fixture if that test +is in the same scope that autouse fixture is defined in (see +:ref:`autouse order`). - test_smtpsimple.py:14: AssertionError - ========================= short test summary info ========================== - FAILED test_smtpsimple.py::test_ehlo - assert 0 - ============================ 1 failed in 0.12s ============================= +A fixture can also request any other fixture, no matter where it's defined, so +long as the test requesting them can see all fixtures involved. -In the failure traceback we see that the test function was called with a -``smtp_connection`` argument, the ``smtplib.SMTP()`` instance created by the fixture -function. The test function fails on our deliberate ``assert 0``. Here is -the exact protocol used by ``pytest`` to call the test function this way: +For example, here's a test file with a fixture (``outer``) that requests a +fixture (``inner``) from a scope it wasn't defined in: -1. pytest :ref:`finds ` the test ``test_ehlo`` because - of the ``test_`` prefix. The test function needs a function argument - named ``smtp_connection``. A matching fixture function is discovered by - looking for a fixture-marked function named ``smtp_connection``. +.. literalinclude:: example/fixtures/test_fixtures_request_different_scope.py -2. ``smtp_connection()`` is called to create an instance. +From the tests' perspectives, they have no problem seeing each of the fixtures +they're dependent on: -3. ``test_ehlo()`` is called and fails in the last - line of the test function. +.. image:: example/fixtures/test_fixtures_request_different_scope.svg + :align: center -Note that if you misspell a function argument or want -to use one that isn't available, you'll see an error -with a list of available function arguments. +So when they run, ``outer`` will have no problem finding ``inner``, because +pytest searched from the tests' perspectives. .. note:: + The scope a fixture is defined in has no bearing on the order it will be + instantiated in: the order is mandated by the logic described + :ref:`here `. - You can always issue: +``conftest.py``: sharing fixtures across multiple files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - .. code-block:: bash +The ``conftest.py`` file serves as a means of providing fixtures for an entire +directory. Fixtures defined in a ``conftest.py`` can be used by any test +in that package without needing to import them (pytest will automatically +discover them). - pytest --fixtures test_simplefactory.py +You can have multiple nested directories/packages containing your tests, and +each directory can have its own ``conftest.py`` with its own fixtures, adding on +to the ones provided by the ``conftest.py`` files in parent directories. - to see available fixtures (fixtures with leading ``_`` are only shown if you add the ``-v`` option). +For example, given a test file structure like this: -Fixtures: a prime example of dependency injection ---------------------------------------------------- +:: -Fixtures allow test functions to easily receive and work -against specific pre-initialized application objects without having -to care about import/setup/cleanup details. -It's a prime example of `dependency injection`_ where fixture -functions take the role of the *injector* and test functions are the -*consumers* of fixture objects. + tests/ + __init__.py -.. _`conftest.py`: -.. _`conftest`: + conftest.py + # content of tests/conftest.py + import pytest -``conftest.py``: sharing fixture functions ------------------------------------------- + @pytest.fixture + def order(): + return [] -If during implementing your tests you realize that you -want to use a fixture function from multiple test files you can move it -to a ``conftest.py`` file. -You don't need to import the fixture you want to use in a test, it -automatically gets discovered by pytest. The discovery of -fixture functions starts at test classes, then test modules, then -``conftest.py`` files and finally builtin and third party plugins. + @pytest.fixture + def top(order, innermost): + order.append("top") -You can also use the ``conftest.py`` file to implement -:ref:`local per-directory plugins `. + test_top.py + # content of tests/test_top.py + import pytest -Sharing test data ------------------ + @pytest.fixture + def innermost(order): + order.append("innermost top") -If you want to make test data from files available to your tests, a good way -to do this is by loading these data in a fixture for use by your tests. -This makes use of the automatic caching mechanisms of pytest. + def test_order(order, top): + assert order == ["innermost top", "top"] -Another good approach is by adding the data files in the ``tests`` folder. -There are also community plugins available to help managing this aspect of -testing, e.g. `pytest-datadir `__ -and `pytest-datafiles `__. + subpackage/ + __init__.py -.. _smtpshared: + conftest.py + # content of tests/subpackage/conftest.py + import pytest -Scope: sharing fixtures across classes, modules, packages or session --------------------------------------------------------------------- + @pytest.fixture + def mid(order): + order.append("mid subpackage") -.. regendoc:wipe + test_subpackage.py + # content of tests/subpackage/test_subpackage.py + import pytest -Fixtures requiring network access depend on connectivity and are -usually time-expensive to create. Extending the previous example, we -can add a ``scope="module"`` parameter to the -:py:func:`@pytest.fixture ` invocation -to cause the decorated ``smtp_connection`` fixture function to only be invoked -once per test *module* (the default is to invoke once per test *function*). -Multiple test functions in a test module will thus -each receive the same ``smtp_connection`` fixture instance, thus saving time. -Possible values for ``scope`` are: ``function``, ``class``, ``module``, ``package`` or ``session``. + @pytest.fixture + def innermost(order, mid): + order.append("innermost subpackage") -The next example puts the fixture function into a separate ``conftest.py`` file -so that tests from multiple test modules in the directory can -access the fixture function: + def test_order(order, top): + assert order == ["mid subpackage", "innermost subpackage", "top"] -.. code-block:: python +The boundaries of the scopes can be visualized like this: - # content of conftest.py - import pytest - import smtplib +.. image:: example/fixtures/fixture_availability.svg + :align: center +The directories become their own sort of scope where fixtures that are defined +in a ``conftest.py`` file in that directory become available for that whole +scope. - @pytest.fixture(scope="module") - def smtp_connection(): - return smtplib.SMTP("smtp.gmail.com", 587, timeout=5) +Tests are allowed to search upward (stepping outside a circle) for fixtures, but +can never go down (stepping inside a circle) to continue their search. So +``tests/subpackage/test_subpackage.py::test_order`` would be able to find the +``innermost`` fixture defined in ``tests/subpackage/test_subpackage.py``, but +the one defined in ``tests/test_top.py`` would be unavailable to it because it +would have to step down a level (step inside a circle) to find it. -The name of the fixture again is ``smtp_connection`` and you can access its -result by listing the name ``smtp_connection`` as an input parameter in any -test or fixture function (in or below the directory where ``conftest.py`` is -located): +The first fixture the test finds is the one that will be used, so +:ref:`fixtures can be overriden ` if you need to change or +extend what one does for a particular scope. -.. code-block:: python +You can also use the ``conftest.py`` file to implement +:ref:`local per-directory plugins `. - # content of test_module.py +Fixtures from third-party plugins +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Fixtures don't have to be defined in this structure to be available for tests, +though. They can also be provided by third-party plugins that are installed, and +this is how many pytest plugins operate. As long as those plugins are installed, +the fixtures they provide can be requested from anywhere in your test suite. - def test_ehlo(smtp_connection): - response, msg = smtp_connection.ehlo() - assert response == 250 - assert b"smtp.gmail.com" in msg - assert 0 # for demo purposes +Because they're provided from outside the structure of your test suite, +third-party plugins don't really provide a scope like `conftest.py` files and +the directories in your test suite do. As a result, pytest will search for +fixtures stepping out through scopes as explained previously, only reaching +fixtures defined in plugins *last*. +For example, given the following file structure: - def test_noop(smtp_connection): - response, msg = smtp_connection.noop() - assert response == 250 - assert 0 # for demo purposes +:: -We deliberately insert failing ``assert 0`` statements in order to -inspect what is going on and can now run the tests: + tests/ + __init__.py -.. code-block:: pytest + conftest.py + # content of tests/conftest.py + import pytest - $ pytest test_module.py - =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y - cachedir: $PYTHON_PREFIX/.pytest_cache - rootdir: $REGENDOC_TMPDIR - collected 2 items + @pytest.fixture + def order(): + return [] - test_module.py FF [100%] + subpackage/ + __init__.py - ================================= FAILURES ================================= - ________________________________ test_ehlo _________________________________ + conftest.py + # content of tests/subpackage/conftest.py + import pytest - smtp_connection = + @pytest.fixture(autouse=True) + def mid(order, b_fix): + order.append("mid subpackage") - def test_ehlo(smtp_connection): - response, msg = smtp_connection.ehlo() - assert response == 250 - assert b"smtp.gmail.com" in msg - > assert 0 # for demo purposes - E assert 0 + test_subpackage.py + # content of tests/subpackage/test_subpackage.py + import pytest - test_module.py:7: AssertionError - ________________________________ test_noop _________________________________ + @pytest.fixture + def inner(order, mid, a_fix): + order.append("inner subpackage") - smtp_connection = + def test_order(order, inner): + assert order == ["b_fix", "mid subpackage", "a_fix", "inner subpackage"] - def test_noop(smtp_connection): - response, msg = smtp_connection.noop() - assert response == 250 - > assert 0 # for demo purposes - E assert 0 +If ``plugin_a`` is installed and provides the fixture ``a_fix``, and +``plugin_b`` is installed and provides the fixture ``b_fix``, then this is what +the test's search for fixtures would look like: - test_module.py:13: AssertionError - ========================= short test summary info ========================== - FAILED test_module.py::test_ehlo - assert 0 - FAILED test_module.py::test_noop - assert 0 - ============================ 2 failed in 0.12s ============================= +.. image:: example/fixtures/fixture_availability_plugins.svg + :align: center -You see the two ``assert 0`` failing and more importantly you can also see -that the same (module-scoped) ``smtp_connection`` object was passed into the -two test functions because pytest shows the incoming argument values in the -traceback. As a result, the two test functions using ``smtp_connection`` run -as quick as a single one because they reuse the same instance. +pytest will only search for ``a_fix`` and ``b_fix`` in the plugins after +searching for them first in the scopes inside ``tests/``. -If you decide that you rather want to have a session-scoped ``smtp_connection`` -instance, you can simply declare it: +.. note: -.. code-block:: python + pytest can tell you what fixtures are available for a given test if you call + ``pytests`` along with the test's name (or the scope it's in), and provide + the ``--fixtures`` flag, e.g. ``pytest --fixtures test_something.py`` + (fixtures with names that start with ``_`` will only be shown if you also + provide the ``-v`` flag). - @pytest.fixture(scope="session") - def smtp_connection(): - # the returned fixture value will be shared for - # all tests needing it - ... +Sharing test data +----------------- +If you want to make test data from files available to your tests, a good way +to do this is by loading these data in a fixture for use by your tests. +This makes use of the automatic caching mechanisms of pytest. -Fixture scopes -^^^^^^^^^^^^^^ +Another good approach is by adding the data files in the ``tests`` folder. +There are also community plugins available to help managing this aspect of +testing, e.g. `pytest-datadir `__ +and `pytest-datafiles `__. -Fixtures are created when first requested by a test, and are destroyed based on their ``scope``: +.. _`fixture order`: -* ``function``: the default scope, the fixture is destroyed at the end of the test. -* ``class``: the fixture is destroyed during teardown of the last test in the class. -* ``module``: the fixture is destroyed during teardown of the last test in the module. -* ``package``: the fixture is destroyed during teardown of the last test in the package. -* ``session``: the fixture is destroyed at the end of the test session. +Fixture instantiation order +--------------------------- -.. note:: +When pytest wants to execute a test, once it knows what fixtures will be +executed, it has to figure out the order they'll be executed in. To do this, it +considers 3 factors: - Pytest only caches one instance of a fixture at a time, which - means that when using a parametrized fixture, pytest may invoke a fixture more than once in - the given scope. +1. scope +2. dependencies +3. autouse -.. _dynamic scope: +Names of fixtures or tests, where they're defined, the order they're defined in, +and the order fixtures are requested in have no bearing on execution order +beyond coincidence. While pytest will try to make sure coincidences like these +stay consistent from run to run, it's not something that should be depended on. +If you want to control the order, it's safest to rely on these 3 things and make +sure dependencies are clearly established. -Dynamic scope -^^^^^^^^^^^^^ +Higher-scoped fixtures are executed first +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. versionadded:: 5.2 +Within a function request for fixtures, those of higher-scopes (such as +``session``) are executed before lower-scoped fixtures (such as ``function`` or +``class``). -In some cases, you might want to change the scope of the fixture without changing the code. -To do that, pass a callable to ``scope``. The callable must return a string with a valid scope -and will be executed only once - during the fixture definition. It will be called with two -keyword arguments - ``fixture_name`` as a string and ``config`` with a configuration object. +Here's an example: -This can be especially useful when dealing with fixtures that need time for setup, like spawning -a docker container. You can use the command-line argument to control the scope of the spawned -containers for different environments. See the example below. +.. literalinclude:: example/fixtures/test_fixtures_order_scope.py -.. code-block:: python +The test will pass because the larger scoped fixtures are executing first. - def determine_scope(fixture_name, config): - if config.getoption("--keep-containers", None): - return "session" - return "function" +The order breaks down to this: +.. image:: example/fixtures/test_fixtures_order_scope.svg + :align: center - @pytest.fixture(scope=determine_scope) - def docker_container(): - yield spawn_container() +Fixtures of the same order execute based on dependencies +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +When a fixture requests another fixture, the other fixture is executed first. +So if fixture ``a`` requests fixture ``b``, fixture ``b`` will execute first, +because ``a`` depends on ``b`` and can't operate without it. Even if ``a`` +doesn't need the result of ``b``, it can still request ``b`` if it needs to make +sure it is executed after ``b``. +For example: -Order: Higher-scoped fixtures are instantiated first ----------------------------------------------------- +.. literalinclude:: example/fixtures/test_fixtures_order_dependencies.py +If we map out what depends on what, we get something that look like this: +.. image:: example/fixtures/test_fixtures_order_dependencies.svg + :align: center -Within a function request for fixtures, those of higher-scopes (such as ``session``) are instantiated before -lower-scoped fixtures (such as ``function`` or ``class``). The relative order of fixtures of same scope follows -the declared order in the test function and honours dependencies between fixtures. Autouse fixtures will be -instantiated before explicitly used fixtures. +The rules provided by each fixture (as to what fixture(s) each one has to come +after) are comprehensive enough that it can be flattened to this: -Consider the code below: +.. image:: example/fixtures/test_fixtures_order_dependencies_flat.svg + :align: center -.. literalinclude:: example/fixtures/test_fixtures_order.py +Enough information has to be provided through these requests in order for pytest +to be able to figure out a clear, linear chain of dependencies, and as a result, +an order of operations for a given test. If there's any ambiguity, and the order +of operations can be interpreted more than one way, you should assume pytest +could go with any one of those interpretations at any point. -The fixtures requested by ``test_order`` will be instantiated in the following order: +For example, if ``d`` didn't request ``c``, i.e.the graph would look like this: -1. ``s1``: is the highest-scoped fixture (``session``). -2. ``m1``: is the second highest-scoped fixture (``module``). -3. ``a1``: is a ``function``-scoped ``autouse`` fixture: it will be instantiated before other fixtures - within the same scope. -4. ``f3``: is a ``function``-scoped fixture, required by ``f1``: it needs to be instantiated at this point -5. ``f1``: is the first ``function``-scoped fixture in ``test_order`` parameter list. -6. ``f2``: is the last ``function``-scoped fixture in ``test_order`` parameter list. +.. image:: example/fixtures/test_fixtures_order_dependencies_unclear.svg + :align: center +Because nothing requested ``c`` other than ``g``, and ``g`` also requests ``f``, +it's now unclear if ``c`` should go before/after ``f``, ``e``, or ``d``. The +only rules that were set for ``c`` is that it must execute after ``b`` and +before ``g``. -.. _`finalization`: +pytest doesn't know where ``c`` should go in the case, so it should be assumed +that it could go anywhere between ``g`` and ``b``. -Fixture finalization / executing teardown code -------------------------------------------------------------- +This isn't necessarily bad, but it's something to keep in mind. If the order +they execute in could affect the behavior a test is targetting, or could +otherwise influence the result of a test, then the order should be defined +explicitely in a way that allows pytest to linearize/"flatten" that order. -pytest supports execution of fixture specific finalization code -when the fixture goes out of scope. By using a ``yield`` statement instead of ``return``, all -the code after the *yield* statement serves as the teardown code: +.. _`autouse order`: -.. code-block:: python +Autouse fixtures are executed first within their scope +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - # content of conftest.py +Autouse fixtures are assumed to apply to every test that could reference them, +so they are executed before other fixtures in that scope. Fixtures that are +requested by autouse fixtures effectively become autouse fixtures themselves for +the tests that the real autouse fixture applies to. - import smtplib - import pytest +So if fixture ``a`` is autouse and fixture ``b`` is not, but fixture ``a`` +requests fixture ``b``, then fixture ``b`` will effectively be an autouse +fixture as well, but only for the tests that ``a`` applies to. +In the last example, the graph became unclear if ``d`` didn't request ``c``. But +if ``c`` was autouse, then ``b`` and ``a`` would effectively also be autouse +because ``c`` depends on them. As a result, they would all be shifted above +non-autouse fixtures within that scope. - @pytest.fixture(scope="module") - def smtp_connection(): - smtp_connection = smtplib.SMTP("smtp.gmail.com", 587, timeout=5) - yield smtp_connection # provide the fixture value - print("teardown smtp") - smtp_connection.close() +So if the test file looked like this: -The ``print`` and ``smtp.close()`` statements will execute when the last test in -the module has finished execution, regardless of the exception status of the -tests. +.. literalinclude:: example/fixtures/test_fixtures_order_autouse.py -Let's execute it: +the graph would look like this: -.. code-block:: pytest +.. image:: example/fixtures/test_fixtures_order_autouse.svg + :align: center - $ pytest -s -q --tb=no - FFteardown smtp +Because ``c`` can now be put above ``d`` in the graph, pytest can once again +linearize the graph to this: - ========================= short test summary info ========================== - FAILED test_module.py::test_ehlo - assert 0 - FAILED test_module.py::test_noop - assert 0 - 2 failed in 0.12s +In this example, ``c`` makes ``b`` and ``a`` effectively autouse fixtures as +well. -We see that the ``smtp_connection`` instance is finalized after the two -tests finished execution. Note that if we decorated our fixture -function with ``scope='function'`` then fixture setup and cleanup would -occur around each single test. In either case the test -module itself does not need to change or know about these details -of fixture setup. +Be careful with autouse, though, as an autouse fixture will automatically +execute for every test that can reach it, even if they don't request it. For +example, consider this file: -Note that we can also seamlessly use the ``yield`` syntax with ``with`` statements: +.. literalinclude:: example/fixtures/test_fixtures_order_autouse_multiple_scopes.py -.. code-block:: python +Even though nothing in ``TestClassWithC1Request`` is requesting ``c1``, it still +is executed for the tests inside it anyway: - # content of test_yield2.py +.. image:: example/fixtures/test_fixtures_order_autouse_multiple_scopes.svg + :align: center - import smtplib - import pytest +But just because one autouse fixture requested a non-autouse fixture, that +doesn't mean the non-autouse fixture becomes an autouse fixture for all contexts +that it can apply to. It only effectively becomes an auotuse fixture for the +contexts the real autouse fixture (the one that requested the non-autouse +fixture) can apply to. +For example, take a look at this test file: - @pytest.fixture(scope="module") - def smtp_connection(): - with smtplib.SMTP("smtp.gmail.com", 587, timeout=5) as smtp_connection: - yield smtp_connection # provide the fixture value +.. literalinclude:: example/fixtures/test_fixtures_order_autouse_temp_effects.py +It would break down to something like this: -The ``smtp_connection`` connection will be closed after the test finished -execution because the ``smtp_connection`` object automatically closes when -the ``with`` statement ends. +.. image:: example/fixtures/test_fixtures_order_autouse_temp_effects.svg + :align: center -Using the contextlib.ExitStack context manager finalizers will always be called -regardless if the fixture *setup* code raises an exception. This is handy to properly -close all resources created by a fixture even if one of them fails to be created/acquired: +For ``test_req`` and ``test_no_req`` inside ``TestClassWithAutouse``, ``c3`` +effectively makes ``c2`` an autouse fixture, which is why ``c2`` and ``c3`` are +executed for both tests, despite not being requested, and why ``c2`` and ``c3`` +are executed before ``c1`` for ``test_req``. -.. code-block:: python +If this made ``c2`` an *actual* autouse fixture, then ``c2`` would also execute +for the tests inside ``TestClassWithoutAutouse``, since they can reference +``c2`` if they wanted to. But it doesn't, because from the perspective of the +``TestClassWithoutAutouse`` tests, ``c2`` isn't an autouse fixture, since they +can't see ``c3``. - # content of test_yield3.py - import contextlib +.. note: - import pytest + pytest can tell you what order the fixtures will execute in for a given test + if you call ``pytests`` along with the test's name (or the scope it's in), + and provide the ``--setup-plan`` flag, e.g. + ``pytest --setup-plan test_something.py`` (fixtures with names that start + with ``_`` will only be shown if you also provide the ``-v`` flag). - @contextlib.contextmanager - def connect(port): - ... # create connection - yield - ... # close connection +Running multiple ``assert`` statements safely +--------------------------------------------- +Sometimes you may want to run multiple asserts after doing all that setup, which +makes sense as, in more complex systems, a single action can kick off multiple +behaviors. pytest has a convenient way of handling this and it combines a bunch +of what we've gone over so far. - @pytest.fixture - def equipments(): - with contextlib.ExitStack() as stack: - yield [stack.enter_context(connect(port)) for port in ("C1", "C3", "C28")] +All that's needed is stepping up to a larger scope, then having the **act** +step defined as an autouse fixture, and finally, making sure all the fixtures +are targetting that highler level scope. -In the example above, if ``"C28"`` fails with an exception, ``"C1"`` and ``"C3"`` will still -be properly closed. +Let's pull :ref:`an example from above `, and tweak it a +bit. Let's say that in addition to checking for a welcome message in the header, +we also want to check for a sign out button, and a link to the user's profile. -Note that if an exception happens during the *setup* code (before the ``yield`` keyword), the -*teardown* code (after the ``yield``) will not be called. +Let's take a look at how we can structure that so we can run multiple asserts +without having to repeat all those steps again. -An alternative option for executing *teardown* code is to -make use of the ``addfinalizer`` method of the `request-context`_ object to register -finalization functions. +.. note:: -Here's the ``smtp_connection`` fixture changed to use ``addfinalizer`` for cleanup: + For this example, certain fixtures (i.e. ``base_url`` and + ``admin_credentials``) are implied to exist elsewhere. So for now, let's + assume they exist, and we're just not looking at them. .. code-block:: python - # content of conftest.py - import smtplib + # contents of tests/end_to_end/test_login.py + from uuid import uuid4 + from urllib.parse import urljoin + + from selenium.webdriver import Chrome import pytest + from src.utils.pages import LoginPage, LandingPage + from src.utils import AdminApiClient + from src.utils.data_types import User - @pytest.fixture(scope="module") - def smtp_connection(request): - smtp_connection = smtplib.SMTP("smtp.gmail.com", 587, timeout=5) - def fin(): - print("teardown smtp_connection") - smtp_connection.close() + @pytest.fixture(scope="class") + def admin_client(base_url, admin_credentials): + return AdminApiClient(base_url, **admin_credentials) - request.addfinalizer(fin) - return smtp_connection # provide the fixture value + @pytest.fixture(scope="class") + def user(admin_client): + _user = User(name="Susan", username=f"testuser-{uuid4()}", password="P4$$word") + admin_client.create_user(_user) + yield _user + admin_client.delete_user(_user) -Here's the ``equipments`` fixture changed to use ``addfinalizer`` for cleanup: -.. code-block:: python + @pytest.fixture(scope="class") + def driver(): + _driver = Chrome() + yield _driver + _driver.quit() - # content of test_yield3.py - import contextlib - import functools + @pytest.fixture(scope="class") + def landing_page(driver, login): + return LandingPage(driver) - import pytest + class TestLandingPageSuccess: + @pytest.fixture(scope="class", autouse=True) + def login(self, driver, base_url, user): + driver.get(urljoin(base_url, "/login")) + page = LoginPage(driver) + page.login(user) - @contextlib.contextmanager - def connect(port): - ... # create connection - yield - ... # close connection + def test_name_in_header(self, landing_page, user): + assert landing_page.header == f"Welcome, {user.name}!" + def test_sign_out_button(self, landing_page): + assert landing_page.sign_out_button.is_displayed() - @pytest.fixture - def equipments(request): - r = [] - for port in ("C1", "C3", "C28"): - cm = connect(port) - equip = cm.__enter__() - request.addfinalizer(functools.partial(cm.__exit__, None, None, None)) - r.append(equip) - return r + def test_profile_link(self, landing_page, user): + profile_href = urljoin(base_url, f"/profile?id={user.profile_id}") + assert landing_page.profile_link.get_attribute("href") == profile_href +Notice that the methods are only referencing ``self`` in the signature as a +formality. No state is tied to the actual test class as it might be in the +``unittest.TestCase`` framework. Everything is managed by the pytest fixture +system. -Both ``yield`` and ``addfinalizer`` methods work similarly by calling their code after the test -ends. Of course, if an exception happens before the finalize function is registered then it -will not be executed. +Each method only has to request the fixtures that it actually needs without +worrying about order. This is because the **act** fixture is an autouse fixture, +and it made sure all the other fixtures executed before it. There's no more +changes of state that need to take place, so the tests are free to make as many +non-state-changing queries as they want without risking stepping on the toes of +the other tests. + +The ``login`` fixture is defined inside the class as well, because not every one +of the other tests in the module will be expecting a successful login, and the **act** may need to +be handled a little differently for another test class. For example, if we +wanted to write another test scenario around submitting bad credentials, we +could handle it by adding something like this to the test file: + +.. note: + + It's assumed that the page object for this (i.e. ``LoginPage``) raises a + custom exception, ``BadCredentialsException``, when it recognizes text + signifying that on the login form after attempting to log in. + +.. code-block:: python + + class TestLandingPageBadCredentials: + @pytest.fixture(scope="class") + def faux_user(self, user): + _user = deepcopy(user) + _user.password = "badpass" + return _user + + def test_raises_bad_credentials_exception(self, login_page, faux_user): + with pytest.raises(BadCredentialsException): + login_page.login(faux_user) .. _`request-context`: @@ -1239,116 +2267,7 @@ into an ini-file: Currently this will not generate any error or warning, but this is intended to be handled by `#3664 `_. - -.. _`autouse`: -.. _`autouse fixtures`: - -Autouse fixtures (xUnit setup on steroids) ----------------------------------------------------------------------- - -.. regendoc:wipe - -Occasionally, you may want to have fixtures get invoked automatically -without declaring a function argument explicitly or a `usefixtures`_ decorator. -As a practical example, suppose we have a database fixture which has a -begin/rollback/commit architecture and we want to automatically surround -each test method by a transaction and a rollback. Here is a dummy -self-contained implementation of this idea: - -.. code-block:: python - - # content of test_db_transact.py - - import pytest - - - class DB: - def __init__(self): - self.intransaction = [] - - def begin(self, name): - self.intransaction.append(name) - - def rollback(self): - self.intransaction.pop() - - - @pytest.fixture(scope="module") - def db(): - return DB() - - - class TestClass: - @pytest.fixture(autouse=True) - def transact(self, request, db): - db.begin(request.function.__name__) - yield - db.rollback() - - def test_method1(self, db): - assert db.intransaction == ["test_method1"] - - def test_method2(self, db): - assert db.intransaction == ["test_method2"] - -The class-level ``transact`` fixture is marked with *autouse=true* -which implies that all test methods in the class will use this fixture -without a need to state it in the test function signature or with a -class-level ``usefixtures`` decorator. - -If we run it, we get two passing tests: - -.. code-block:: pytest - - $ pytest -q - .. [100%] - 2 passed in 0.12s - -Here is how autouse fixtures work in other scopes: - -- autouse fixtures obey the ``scope=`` keyword-argument: if an autouse fixture - has ``scope='session'`` it will only be run once, no matter where it is - defined. ``scope='class'`` means it will be run once per class, etc. - -- if an autouse fixture is defined in a test module, all its test - functions automatically use it. - -- if an autouse fixture is defined in a conftest.py file then all tests in - all test modules below its directory will invoke the fixture. - -- lastly, and **please use that with care**: if you define an autouse - fixture in a plugin, it will be invoked for all tests in all projects - where the plugin is installed. This can be useful if a fixture only - anyway works in the presence of certain settings e. g. in the ini-file. Such - a global fixture should always quickly determine if it should do - any work and avoid otherwise expensive imports or computation. - -Note that the above ``transact`` fixture may very well be a fixture that -you want to make available in your project without having it generally -active. The canonical way to do that is to put the transact definition -into a conftest.py file **without** using ``autouse``: - -.. code-block:: python - - # content of conftest.py - @pytest.fixture - def transact(request, db): - db.begin() - yield - db.rollback() - -and then e.g. have a TestClass using it by declaring the need: - -.. code-block:: python - - @pytest.mark.usefixtures("transact") - class TestClass: - def test_method1(self): - ... - -All test methods in this TestClass will use the transaction fixture while -other test classes or functions in the module will not use it unless -they also add a ``transact`` reference. +.. _`override fixtures`: Overriding fixtures on various levels ------------------------------------- From 8255effc5b17594987f77c3165b022ee31e2e70a Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 16 Dec 2020 15:43:58 -0300 Subject: [PATCH 0018/2772] Remove Travis badge from README Since we stopped testing Python 3.5, we no longer use Travis. --- README.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.rst b/README.rst index 0fb4e363b1c..46b07e59d15 100644 --- a/README.rst +++ b/README.rst @@ -20,9 +20,6 @@ :target: https://codecov.io/gh/pytest-dev/pytest :alt: Code coverage Status -.. image:: https://travis-ci.org/pytest-dev/pytest.svg?branch=master - :target: https://travis-ci.org/pytest-dev/pytest - .. image:: https://github.com/pytest-dev/pytest/workflows/main/badge.svg :target: https://github.com/pytest-dev/pytest/actions?query=workflow%3Amain From 3eef150f2e2a2d2eee3c41a18e10e37a148d4e3b Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 17 Dec 2020 08:19:50 -0300 Subject: [PATCH 0019/2772] Remove other references to Travis --- scripts/append_codecov_token.py | 4 ++-- scripts/publish-gh-release-notes.py | 9 +++------ tox.ini | 3 +-- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/scripts/append_codecov_token.py b/scripts/append_codecov_token.py index 8eecb0fa51a..5c617aafb54 100644 --- a/scripts/append_codecov_token.py +++ b/scripts/append_codecov_token.py @@ -1,8 +1,8 @@ """ Appends the codecov token to the 'codecov.yml' file at the root of the repository. -This is done by CI during PRs and builds on the pytest-dev repository so we can upload coverage, at least -until codecov grows some native integration like it has with Travis and AppVeyor. +This is done by CI during PRs and builds on the pytest-dev repository so we can +upload coverage, at least until codecov grows some native integration with GitHub Actions. See discussion in https://github.com/pytest-dev/pytest/pull/6441 for more information. """ diff --git a/scripts/publish-gh-release-notes.py b/scripts/publish-gh-release-notes.py index 2531b0221b9..68cbd7adffd 100644 --- a/scripts/publish-gh-release-notes.py +++ b/scripts/publish-gh-release-notes.py @@ -1,7 +1,7 @@ """ Script used to publish GitHub release notes extracted from CHANGELOG.rst. -This script is meant to be executed after a successful deployment in Travis. +This script is meant to be executed after a successful deployment in GitHub actions. Uses the following environment variables: @@ -12,11 +12,8 @@ https://github.com/settings/tokens - It should be encrypted using: - - $travis encrypt GH_RELEASE_NOTES_TOKEN= -r pytest-dev/pytest - - And the contents pasted in the ``deploy.env.secure`` section in the ``travis.yml`` file. + This token should be set in a secret in the repository, which is exposed as an + environment variable in the main.yml workflow file. The script also requires ``pandoc`` to be previously installed in the system. diff --git a/tox.ini b/tox.ini index 43e151c07aa..908f56ea681 100644 --- a/tox.ini +++ b/tox.ini @@ -2,7 +2,6 @@ isolated_build = True minversion = 3.20.0 distshare = {homedir}/.tox/distshare -# make sure to update environment list in travis.yml and appveyor.yml envlist = linting py36 @@ -23,7 +22,7 @@ commands = doctesting: {env:_PYTEST_TOX_COVERAGE_RUN:} pytest --doctest-modules --pyargs _pytest coverage: coverage combine coverage: coverage report -m -passenv = USER USERNAME COVERAGE_* TRAVIS PYTEST_ADDOPTS TERM +passenv = USER USERNAME COVERAGE_* PYTEST_ADDOPTS TERM setenv = _PYTEST_TOX_DEFAULT_POSARGS={env:_PYTEST_TOX_POSARGS_DOCTESTING:} {env:_PYTEST_TOX_POSARGS_LSOF:} {env:_PYTEST_TOX_POSARGS_XDIST:} From bd894e3065ba6fa13327ad5dfc94f2b6208cf0ff Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Thu, 17 Dec 2020 16:55:36 +0000 Subject: [PATCH 0020/2772] Add Changelog to setup.cfg (#8166) Co-authored-by: Thomas Grainger Co-authored-by: Bruno Oliveira --- setup.cfg | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.cfg b/setup.cfg index 09c07d5bb6c..14fdb6df5c0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,6 +26,8 @@ classifiers = Topic :: Utilities keywords = test, unittest project_urls = + Changelog=https://docs.pytest.org/en/stable/changelog.html + Twitter=https://twitter.com/pytestdotorg Source=https://github.com/pytest-dev/pytest Tracker=https://github.com/pytest-dev/pytest/issues From 1264404fe712de864aa365416cee648f3cd56128 Mon Sep 17 00:00:00 2001 From: antonblr Date: Thu, 17 Dec 2020 21:01:20 -0800 Subject: [PATCH 0021/2772] infra: Temporary pin setup-python GH action to v2.1.4 --- .github/workflows/main.yml | 9 ++++++--- .github/workflows/prepare-release-pr.yml | 3 ++- .github/workflows/release-on-comment.yml | 3 ++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2b779279fdc..4ae366a7c40 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -123,7 +123,8 @@ jobs: with: fetch-depth: 0 - name: Set up Python ${{ matrix.python }} - uses: actions/setup-python@v2 + # TODO: Use "v2" tag once https://github.com/actions/setup-python/issues/171 is resolved. + uses: actions/setup-python@v2.1.4 with: python-version: ${{ matrix.python }} - name: Install dependencies @@ -158,7 +159,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + # TODO: Use "v2" tag once https://github.com/actions/setup-python/issues/171 is resolved. + - uses: actions/setup-python@v2.1.4 - name: set PY run: echo "name=PY::$(python -c 'import hashlib, sys;print(hashlib.sha256(sys.version.encode()+sys.executable.encode()).hexdigest())')" >> $GITHUB_ENV - uses: actions/cache@v2 @@ -184,7 +186,8 @@ jobs: with: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v2 + # TODO: Use "v2" tag once https://github.com/actions/setup-python/issues/171 is resolved. + uses: actions/setup-python@v2.1.4 with: python-version: "3.7" - name: Install dependencies diff --git a/.github/workflows/prepare-release-pr.yml b/.github/workflows/prepare-release-pr.yml index dec35236430..e4bf51d1184 100644 --- a/.github/workflows/prepare-release-pr.yml +++ b/.github/workflows/prepare-release-pr.yml @@ -22,7 +22,8 @@ jobs: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v2 + # TODO: Use "v2" tag once https://github.com/actions/setup-python/issues/171 is resolved. + uses: actions/setup-python@v2.1.4 with: python-version: "3.8" diff --git a/.github/workflows/release-on-comment.yml b/.github/workflows/release-on-comment.yml index 94863d896b9..f51cd86e9ee 100644 --- a/.github/workflows/release-on-comment.yml +++ b/.github/workflows/release-on-comment.yml @@ -19,7 +19,8 @@ jobs: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v2 + # TODO: Use "v2" tag once https://github.com/actions/setup-python/issues/171 is resolved. + uses: actions/setup-python@v2.1.4 with: python-version: "3.8" - name: Install dependencies From 4da445dc2e9212f724ae0dc0f7c6fdf465801a8e Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Fri, 18 Dec 2020 10:44:20 -0800 Subject: [PATCH 0022/2772] Revert "infra: Temporary pin setup-python GH action to v2.1.4" --- .github/workflows/main.yml | 9 +++------ .github/workflows/prepare-release-pr.yml | 3 +-- .github/workflows/release-on-comment.yml | 3 +-- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4ae366a7c40..2b779279fdc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -123,8 +123,7 @@ jobs: with: fetch-depth: 0 - name: Set up Python ${{ matrix.python }} - # TODO: Use "v2" tag once https://github.com/actions/setup-python/issues/171 is resolved. - uses: actions/setup-python@v2.1.4 + uses: actions/setup-python@v2 with: python-version: ${{ matrix.python }} - name: Install dependencies @@ -159,8 +158,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - # TODO: Use "v2" tag once https://github.com/actions/setup-python/issues/171 is resolved. - - uses: actions/setup-python@v2.1.4 + - uses: actions/setup-python@v2 - name: set PY run: echo "name=PY::$(python -c 'import hashlib, sys;print(hashlib.sha256(sys.version.encode()+sys.executable.encode()).hexdigest())')" >> $GITHUB_ENV - uses: actions/cache@v2 @@ -186,8 +184,7 @@ jobs: with: fetch-depth: 0 - name: Set up Python - # TODO: Use "v2" tag once https://github.com/actions/setup-python/issues/171 is resolved. - uses: actions/setup-python@v2.1.4 + uses: actions/setup-python@v2 with: python-version: "3.7" - name: Install dependencies diff --git a/.github/workflows/prepare-release-pr.yml b/.github/workflows/prepare-release-pr.yml index e4bf51d1184..dec35236430 100644 --- a/.github/workflows/prepare-release-pr.yml +++ b/.github/workflows/prepare-release-pr.yml @@ -22,8 +22,7 @@ jobs: fetch-depth: 0 - name: Set up Python - # TODO: Use "v2" tag once https://github.com/actions/setup-python/issues/171 is resolved. - uses: actions/setup-python@v2.1.4 + uses: actions/setup-python@v2 with: python-version: "3.8" diff --git a/.github/workflows/release-on-comment.yml b/.github/workflows/release-on-comment.yml index f51cd86e9ee..94863d896b9 100644 --- a/.github/workflows/release-on-comment.yml +++ b/.github/workflows/release-on-comment.yml @@ -19,8 +19,7 @@ jobs: fetch-depth: 0 - name: Set up Python - # TODO: Use "v2" tag once https://github.com/actions/setup-python/issues/171 is resolved. - uses: actions/setup-python@v2.1.4 + uses: actions/setup-python@v2 with: python-version: "3.8" - name: Install dependencies From 293a7c962da6aedbccf6ce4fc6fecf345e835432 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Fri, 18 Dec 2020 10:45:28 -0800 Subject: [PATCH 0023/2772] Use new pypy3 github actions syntax --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2b779279fdc..beb50178528 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -89,7 +89,7 @@ jobs: os: ubuntu-latest tox_env: "py39-xdist" - name: "ubuntu-pypy3" - python: "pypy3" + python: "pypy-3.7" os: ubuntu-latest tox_env: "pypy3-xdist" From 15156e94c49da11c6fc5a57d576d655cc7794fdf Mon Sep 17 00:00:00 2001 From: antonblr Date: Tue, 15 Dec 2020 20:16:05 -0800 Subject: [PATCH 0024/2772] tests: Migrate to pytester - final update --- .github/workflows/main.yml | 3 +- src/_pytest/nodes.py | 2 +- src/_pytest/python.py | 2 +- testing/deprecated_test.py | 35 ++- testing/test_cacheprovider.py | 2 +- testing/test_collection.py | 25 +- testing/test_debugging.py | 5 +- testing/test_junitxml.py | 425 ++++++++++++++++++++-------------- testing/test_link_resolve.py | 29 ++- testing/test_main.py | 29 ++- testing/test_parseopt.py | 2 +- testing/test_pytester.py | 273 +++++++++++----------- testing/test_unittest.py | 387 ++++++++++++++++--------------- 13 files changed, 656 insertions(+), 563 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index beb50178528..1b6e85fd87e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -123,7 +123,8 @@ jobs: with: fetch-depth: 0 - name: Set up Python ${{ matrix.python }} - uses: actions/setup-python@v2 + # https://github.com/actions/setup-python/issues/171 + uses: actions/setup-python@v2.1.4 with: python-version: ${{ matrix.python }} - name: Install dependencies diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index fee0770eb2b..27c76a04302 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -528,7 +528,7 @@ def gethookproxy(self, fspath: "os.PathLike[str]"): warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2) return self.session.gethookproxy(fspath) - def isinitpath(self, path: py.path.local) -> bool: + def isinitpath(self, path: "os.PathLike[str]") -> bool: warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2) return self.session.isinitpath(path) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 18e449b9361..018e368f45e 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -660,7 +660,7 @@ def gethookproxy(self, fspath: "os.PathLike[str]"): warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2) return self.session.gethookproxy(fspath) - def isinitpath(self, path: py.path.local) -> bool: + def isinitpath(self, path: "os.PathLike[str]") -> bool: warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2) return self.session.isinitpath(path) diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index d213414ee45..6d92d181f99 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -5,24 +5,23 @@ import pytest from _pytest import deprecated from _pytest.pytester import Pytester -from _pytest.pytester import Testdir @pytest.mark.parametrize("attribute", pytest.collect.__all__) # type: ignore # false positive due to dynamic attribute -def test_pytest_collect_module_deprecated(attribute): +def test_pytest_collect_module_deprecated(attribute) -> None: with pytest.warns(DeprecationWarning, match=attribute): getattr(pytest.collect, attribute) @pytest.mark.parametrize("plugin", sorted(deprecated.DEPRECATED_EXTERNAL_PLUGINS)) @pytest.mark.filterwarnings("default") -def test_external_plugins_integrated(testdir, plugin): - testdir.syspathinsert() - testdir.makepyfile(**{plugin: ""}) +def test_external_plugins_integrated(pytester: Pytester, plugin) -> None: + pytester.syspathinsert() + pytester.makepyfile(**{plugin: ""}) with pytest.warns(pytest.PytestConfigWarning): - testdir.parseconfig("-p", plugin) + pytester.parseconfig("-p", plugin) def test_fillfuncargs_is_deprecated() -> None: @@ -49,32 +48,32 @@ def test_fillfixtures_is_deprecated() -> None: _pytest.fixtures.fillfixtures(mock.Mock()) -def test_minus_k_dash_is_deprecated(testdir) -> None: - threepass = testdir.makepyfile( +def test_minus_k_dash_is_deprecated(pytester: Pytester) -> None: + threepass = pytester.makepyfile( test_threepass=""" def test_one(): assert 1 def test_two(): assert 1 def test_three(): assert 1 """ ) - result = testdir.runpytest("-k=-test_two", threepass) + result = pytester.runpytest("-k=-test_two", threepass) result.stdout.fnmatch_lines(["*The `-k '-expr'` syntax*deprecated*"]) -def test_minus_k_colon_is_deprecated(testdir) -> None: - threepass = testdir.makepyfile( +def test_minus_k_colon_is_deprecated(pytester: Pytester) -> None: + threepass = pytester.makepyfile( test_threepass=""" def test_one(): assert 1 def test_two(): assert 1 def test_three(): assert 1 """ ) - result = testdir.runpytest("-k", "test_two:", threepass) + result = pytester.runpytest("-k", "test_two:", threepass) result.stdout.fnmatch_lines(["*The `-k 'expr:'` syntax*deprecated*"]) -def test_fscollector_gethookproxy_isinitpath(testdir: Testdir) -> None: - module = testdir.getmodulecol( +def test_fscollector_gethookproxy_isinitpath(pytester: Pytester) -> None: + module = pytester.getmodulecol( """ def test_foo(): pass """, @@ -85,16 +84,16 @@ def test_foo(): pass assert isinstance(package, pytest.Package) with pytest.warns(pytest.PytestDeprecationWarning, match="gethookproxy"): - package.gethookproxy(testdir.tmpdir) + package.gethookproxy(pytester.path) with pytest.warns(pytest.PytestDeprecationWarning, match="isinitpath"): - package.isinitpath(testdir.tmpdir) + package.isinitpath(pytester.path) # The methods on Session are *not* deprecated. session = module.session with warnings.catch_warnings(record=True) as rec: - session.gethookproxy(testdir.tmpdir) - session.isinitpath(testdir.tmpdir) + session.gethookproxy(pytester.path) + session.isinitpath(pytester.path) assert len(rec) == 0 diff --git a/testing/test_cacheprovider.py b/testing/test_cacheprovider.py index 7f0827bd488..ebd455593f3 100644 --- a/testing/test_cacheprovider.py +++ b/testing/test_cacheprovider.py @@ -1050,7 +1050,7 @@ def test_packages(self, pytester: Pytester) -> None: class TestNewFirst: - def test_newfirst_usecase(self, pytester: Pytester, testdir) -> None: + def test_newfirst_usecase(self, pytester: Pytester) -> None: pytester.makepyfile( **{ "test_1/test_1.py": """ diff --git a/testing/test_collection.py b/testing/test_collection.py index 862c1aba8d2..2d03fda39de 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -6,6 +6,8 @@ from pathlib import Path from typing import List +import py.path + import pytest from _pytest.config import ExitCode from _pytest.fixtures import FixtureRequest @@ -16,7 +18,6 @@ from _pytest.pathlib import symlink_or_skip from _pytest.pytester import HookRecorder from _pytest.pytester import Pytester -from _pytest.pytester import Testdir def ensure_file(file_path: Path) -> Path: @@ -206,15 +207,17 @@ def test_ignored_virtualenvs_norecursedirs_precedence( "Activate.ps1", ), ) - def test__in_venv(self, testdir: Testdir, fname: str) -> None: + def test__in_venv(self, pytester: Pytester, fname: str) -> None: """Directly test the virtual env detection function""" bindir = "Scripts" if sys.platform.startswith("win") else "bin" # no bin/activate, not a virtualenv - base_path = testdir.tmpdir.mkdir("venv") - assert _in_venv(base_path) is False + base_path = pytester.mkdir("venv") + assert _in_venv(py.path.local(base_path)) is False # with bin/activate, totally a virtualenv - base_path.ensure(bindir, fname) - assert _in_venv(base_path) is True + bin_path = base_path.joinpath(bindir) + bin_path.mkdir() + bin_path.joinpath(fname).touch() + assert _in_venv(py.path.local(base_path)) is True def test_custom_norecursedirs(self, pytester: Pytester) -> None: pytester.makeini( @@ -264,7 +267,7 @@ def test_testpaths_ini(self, pytester: Pytester, monkeypatch: MonkeyPatch) -> No class TestCollectPluginHookRelay: - def test_pytest_collect_file(self, testdir: Testdir) -> None: + def test_pytest_collect_file(self, pytester: Pytester) -> None: wascalled = [] class Plugin: @@ -273,8 +276,8 @@ def pytest_collect_file(self, path): # Ignore hidden files, e.g. .testmondata. wascalled.append(path) - testdir.makefile(".abc", "xyz") - pytest.main(testdir.tmpdir, plugins=[Plugin()]) + pytester.makefile(".abc", "xyz") + pytest.main(py.path.local(pytester.path), plugins=[Plugin()]) assert len(wascalled) == 1 assert wascalled[0].ext == ".abc" @@ -1336,7 +1339,7 @@ def test_does_not_put_src_on_path(pytester: Pytester) -> None: assert result.ret == ExitCode.OK -def test_fscollector_from_parent(testdir: Testdir, request: FixtureRequest) -> None: +def test_fscollector_from_parent(pytester: Pytester, request: FixtureRequest) -> None: """Ensure File.from_parent can forward custom arguments to the constructor. Context: https://github.com/pytest-dev/pytest-cpp/pull/47 @@ -1352,7 +1355,7 @@ def from_parent(cls, parent, *, fspath, x): return super().from_parent(parent=parent, fspath=fspath, x=x) collector = MyCollector.from_parent( - parent=request.session, fspath=testdir.tmpdir / "foo", x=10 + parent=request.session, fspath=py.path.local(pytester.path) / "foo", x=10 ) assert collector.x == 10 diff --git a/testing/test_debugging.py b/testing/test_debugging.py index ed96f7ec781..8218b7a0ede 100644 --- a/testing/test_debugging.py +++ b/testing/test_debugging.py @@ -21,11 +21,10 @@ @pytest.fixture(autouse=True) -def pdb_env(request): +def pdb_env(request, monkeypatch: MonkeyPatch): if "pytester" in request.fixturenames: # Disable pdb++ with inner tests. - pytester = request.getfixturevalue("testdir") - pytester.monkeypatch.setenv("PDBPP_HIJACK_PDB", "0") + monkeypatch.setenv("PDBPP_HIJACK_PDB", "0") def runpdb_and_get_report(pytester: Pytester, source: str): diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index 006bea96280..3e445dcefc5 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -4,24 +4,31 @@ from pathlib import Path from typing import cast from typing import List +from typing import Optional from typing import Tuple from typing import TYPE_CHECKING +from typing import TypeVar +from typing import Union from xml.dom import minidom -import py import xmlschema import pytest from _pytest.config import Config from _pytest.junitxml import bin_xml_escape from _pytest.junitxml import LogXML +from _pytest.monkeypatch import MonkeyPatch +from _pytest.pytester import Pytester +from _pytest.pytester import RunResult from _pytest.reports import BaseReport from _pytest.reports import TestReport from _pytest.store import Store +T = TypeVar("T") + @pytest.fixture(scope="session") -def schema(): +def schema() -> xmlschema.XMLSchema: """Return an xmlschema.XMLSchema object for the junit-10.xsd file.""" fn = Path(__file__).parent / "example_scripts/junit-10.xsd" with fn.open() as f: @@ -29,7 +36,7 @@ def schema(): @pytest.fixture -def run_and_parse(testdir, schema): +def run_and_parse(pytester: Pytester, schema: xmlschema.XMLSchema) -> T: """Fixture that returns a function that can be used to execute pytest and return the parsed ``DomNode`` of the root xml node. @@ -37,18 +44,20 @@ def run_and_parse(testdir, schema): "xunit2" is also automatically validated against the schema. """ - def run(*args, family="xunit1"): + def run( + *args: Union[str, "os.PathLike[str]"], family: Optional[str] = "xunit1", + ) -> Tuple[RunResult, "DomNode"]: if family: args = ("-o", "junit_family=" + family) + args - xml_path = testdir.tmpdir.join("junit.xml") - result = testdir.runpytest("--junitxml=%s" % xml_path, *args) + xml_path = pytester.path.joinpath("junit.xml") + result = pytester.runpytest("--junitxml=%s" % xml_path, *args) if family == "xunit2": with xml_path.open() as f: schema.validate(f) xmldoc = minidom.parse(str(xml_path)) return result, DomNode(xmldoc) - return run + return cast(T, run) def assert_attr(node, **kwargs): @@ -130,8 +139,10 @@ def next_sibling(self): class TestPython: @parametrize_families - def test_summing_simple(self, testdir, run_and_parse, xunit_family): - testdir.makepyfile( + def test_summing_simple( + self, pytester: Pytester, run_and_parse, xunit_family + ) -> None: + pytester.makepyfile( """ import pytest def test_pass(): @@ -154,8 +165,10 @@ def test_xpass(): node.assert_attr(name="pytest", errors=0, failures=1, skipped=2, tests=5) @parametrize_families - def test_summing_simple_with_errors(self, testdir, run_and_parse, xunit_family): - testdir.makepyfile( + def test_summing_simple_with_errors( + self, pytester: Pytester, run_and_parse, xunit_family + ) -> None: + pytester.makepyfile( """ import pytest @pytest.fixture @@ -181,8 +194,10 @@ def test_xpass(): node.assert_attr(name="pytest", errors=1, failures=2, skipped=1, tests=5) @parametrize_families - def test_hostname_in_xml(self, testdir, run_and_parse, xunit_family): - testdir.makepyfile( + def test_hostname_in_xml( + self, pytester: Pytester, run_and_parse, xunit_family + ) -> None: + pytester.makepyfile( """ def test_pass(): pass @@ -193,8 +208,10 @@ def test_pass(): node.assert_attr(hostname=platform.node()) @parametrize_families - def test_timestamp_in_xml(self, testdir, run_and_parse, xunit_family): - testdir.makepyfile( + def test_timestamp_in_xml( + self, pytester: Pytester, run_and_parse, xunit_family + ) -> None: + pytester.makepyfile( """ def test_pass(): pass @@ -206,8 +223,10 @@ def test_pass(): timestamp = datetime.strptime(node["timestamp"], "%Y-%m-%dT%H:%M:%S.%f") assert start_time <= timestamp < datetime.now() - def test_timing_function(self, testdir, run_and_parse, mock_timing): - testdir.makepyfile( + def test_timing_function( + self, pytester: Pytester, run_and_parse, mock_timing + ) -> None: + pytester.makepyfile( """ from _pytest import timing def setup_module(): @@ -226,8 +245,12 @@ def test_sleep(): @pytest.mark.parametrize("duration_report", ["call", "total"]) def test_junit_duration_report( - self, testdir, monkeypatch, duration_report, run_and_parse - ): + self, + pytester: Pytester, + monkeypatch: MonkeyPatch, + duration_report, + run_and_parse, + ) -> None: # mock LogXML.node_reporter so it always sets a known duration to each test report object original_node_reporter = LogXML.node_reporter @@ -239,7 +262,7 @@ def node_reporter_wrapper(s, report): monkeypatch.setattr(LogXML, "node_reporter", node_reporter_wrapper) - testdir.makepyfile( + pytester.makepyfile( """ def test_foo(): pass @@ -256,8 +279,8 @@ def test_foo(): assert val == 1.0 @parametrize_families - def test_setup_error(self, testdir, run_and_parse, xunit_family): - testdir.makepyfile( + def test_setup_error(self, pytester: Pytester, run_and_parse, xunit_family) -> None: + pytester.makepyfile( """ import pytest @@ -279,8 +302,10 @@ def test_function(arg): assert "ValueError" in fnode.toxml() @parametrize_families - def test_teardown_error(self, testdir, run_and_parse, xunit_family): - testdir.makepyfile( + def test_teardown_error( + self, pytester: Pytester, run_and_parse, xunit_family + ) -> None: + pytester.makepyfile( """ import pytest @@ -302,8 +327,10 @@ def test_function(arg): assert "ValueError" in fnode.toxml() @parametrize_families - def test_call_failure_teardown_error(self, testdir, run_and_parse, xunit_family): - testdir.makepyfile( + def test_call_failure_teardown_error( + self, pytester: Pytester, run_and_parse, xunit_family + ) -> None: + pytester.makepyfile( """ import pytest @@ -331,8 +358,10 @@ def test_function(arg): ) @parametrize_families - def test_skip_contains_name_reason(self, testdir, run_and_parse, xunit_family): - testdir.makepyfile( + def test_skip_contains_name_reason( + self, pytester: Pytester, run_and_parse, xunit_family + ) -> None: + pytester.makepyfile( """ import pytest def test_skip(): @@ -349,8 +378,10 @@ def test_skip(): snode.assert_attr(type="pytest.skip", message="hello23") @parametrize_families - def test_mark_skip_contains_name_reason(self, testdir, run_and_parse, xunit_family): - testdir.makepyfile( + def test_mark_skip_contains_name_reason( + self, pytester: Pytester, run_and_parse, xunit_family + ) -> None: + pytester.makepyfile( """ import pytest @pytest.mark.skip(reason="hello24") @@ -371,9 +402,9 @@ def test_skip(): @parametrize_families def test_mark_skipif_contains_name_reason( - self, testdir, run_and_parse, xunit_family - ): - testdir.makepyfile( + self, pytester: Pytester, run_and_parse, xunit_family + ) -> None: + pytester.makepyfile( """ import pytest GLOBAL_CONDITION = True @@ -395,9 +426,9 @@ def test_skip(): @parametrize_families def test_mark_skip_doesnt_capture_output( - self, testdir, run_and_parse, xunit_family - ): - testdir.makepyfile( + self, pytester: Pytester, run_and_parse, xunit_family + ) -> None: + pytester.makepyfile( """ import pytest @pytest.mark.skip(reason="foo") @@ -411,8 +442,10 @@ def test_skip(): assert "bar!" not in node_xml @parametrize_families - def test_classname_instance(self, testdir, run_and_parse, xunit_family): - testdir.makepyfile( + def test_classname_instance( + self, pytester: Pytester, run_and_parse, xunit_family + ) -> None: + pytester.makepyfile( """ class TestClass(object): def test_method(self): @@ -429,9 +462,11 @@ def test_method(self): ) @parametrize_families - def test_classname_nested_dir(self, testdir, run_and_parse, xunit_family): - p = testdir.tmpdir.ensure("sub", "test_hello.py") - p.write("def test_func(): 0/0") + def test_classname_nested_dir( + self, pytester: Pytester, run_and_parse, xunit_family + ) -> None: + p = pytester.mkdir("sub").joinpath("test_hello.py") + p.write_text("def test_func(): 0/0") result, dom = run_and_parse(family=xunit_family) assert result.ret node = dom.find_first_by_tag("testsuite") @@ -440,9 +475,11 @@ def test_classname_nested_dir(self, testdir, run_and_parse, xunit_family): tnode.assert_attr(classname="sub.test_hello", name="test_func") @parametrize_families - def test_internal_error(self, testdir, run_and_parse, xunit_family): - testdir.makeconftest("def pytest_runtest_protocol(): 0 / 0") - testdir.makepyfile("def test_function(): pass") + def test_internal_error( + self, pytester: Pytester, run_and_parse, xunit_family + ) -> None: + pytester.makeconftest("def pytest_runtest_protocol(): 0 / 0") + pytester.makepyfile("def test_function(): pass") result, dom = run_and_parse(family=xunit_family) assert result.ret node = dom.find_first_by_tag("testsuite") @@ -458,9 +495,9 @@ def test_internal_error(self, testdir, run_and_parse, xunit_family): ) @parametrize_families def test_failure_function( - self, testdir, junit_logging, run_and_parse, xunit_family - ): - testdir.makepyfile( + self, pytester: Pytester, junit_logging, run_and_parse, xunit_family + ) -> None: + pytester.makepyfile( """ import logging import sys @@ -521,8 +558,10 @@ def test_fail(): ), "Found unexpected content: system-err" @parametrize_families - def test_failure_verbose_message(self, testdir, run_and_parse, xunit_family): - testdir.makepyfile( + def test_failure_verbose_message( + self, pytester: Pytester, run_and_parse, xunit_family + ) -> None: + pytester.makepyfile( """ import sys def test_fail(): @@ -536,8 +575,10 @@ def test_fail(): fnode.assert_attr(message="AssertionError: An error\nassert 0") @parametrize_families - def test_failure_escape(self, testdir, run_and_parse, xunit_family): - testdir.makepyfile( + def test_failure_escape( + self, pytester: Pytester, run_and_parse, xunit_family + ) -> None: + pytester.makepyfile( """ import pytest @pytest.mark.parametrize('arg1', "<&'", ids="<&'") @@ -564,8 +605,10 @@ def test_func(arg1): assert "%s\n" % char in text @parametrize_families - def test_junit_prefixing(self, testdir, run_and_parse, xunit_family): - testdir.makepyfile( + def test_junit_prefixing( + self, pytester: Pytester, run_and_parse, xunit_family + ) -> None: + pytester.makepyfile( """ def test_func(): assert 0 @@ -586,8 +629,10 @@ def test_hello(self): ) @parametrize_families - def test_xfailure_function(self, testdir, run_and_parse, xunit_family): - testdir.makepyfile( + def test_xfailure_function( + self, pytester: Pytester, run_and_parse, xunit_family + ) -> None: + pytester.makepyfile( """ import pytest def test_xfail(): @@ -604,8 +649,10 @@ def test_xfail(): fnode.assert_attr(type="pytest.xfail", message="42") @parametrize_families - def test_xfailure_marker(self, testdir, run_and_parse, xunit_family): - testdir.makepyfile( + def test_xfailure_marker( + self, pytester: Pytester, run_and_parse, xunit_family + ) -> None: + pytester.makepyfile( """ import pytest @pytest.mark.xfail(reason="42") @@ -625,8 +672,10 @@ def test_xfail(): @pytest.mark.parametrize( "junit_logging", ["no", "log", "system-out", "system-err", "out-err", "all"] ) - def test_xfail_captures_output_once(self, testdir, junit_logging, run_and_parse): - testdir.makepyfile( + def test_xfail_captures_output_once( + self, pytester: Pytester, junit_logging, run_and_parse + ) -> None: + pytester.makepyfile( """ import sys import pytest @@ -652,8 +701,10 @@ def test_fail(): assert len(tnode.find_by_tag("system-out")) == 0 @parametrize_families - def test_xfailure_xpass(self, testdir, run_and_parse, xunit_family): - testdir.makepyfile( + def test_xfailure_xpass( + self, pytester: Pytester, run_and_parse, xunit_family + ) -> None: + pytester.makepyfile( """ import pytest @pytest.mark.xfail @@ -669,8 +720,10 @@ def test_xpass(): tnode.assert_attr(classname="test_xfailure_xpass", name="test_xpass") @parametrize_families - def test_xfailure_xpass_strict(self, testdir, run_and_parse, xunit_family): - testdir.makepyfile( + def test_xfailure_xpass_strict( + self, pytester: Pytester, run_and_parse, xunit_family + ) -> None: + pytester.makepyfile( """ import pytest @pytest.mark.xfail(strict=True, reason="This needs to fail!") @@ -688,8 +741,10 @@ def test_xpass(): fnode.assert_attr(message="[XPASS(strict)] This needs to fail!") @parametrize_families - def test_collect_error(self, testdir, run_and_parse, xunit_family): - testdir.makepyfile("syntax error") + def test_collect_error( + self, pytester: Pytester, run_and_parse, xunit_family + ) -> None: + pytester.makepyfile("syntax error") result, dom = run_and_parse(family=xunit_family) assert result.ret node = dom.find_first_by_tag("testsuite") @@ -699,9 +754,9 @@ def test_collect_error(self, testdir, run_and_parse, xunit_family): fnode.assert_attr(message="collection failure") assert "SyntaxError" in fnode.toxml() - def test_unicode(self, testdir, run_and_parse): + def test_unicode(self, pytester: Pytester, run_and_parse) -> None: value = "hx\xc4\x85\xc4\x87\n" - testdir.makepyfile( + pytester.makepyfile( """\ # coding: latin1 def test_hello(): @@ -716,9 +771,9 @@ def test_hello(): fnode = tnode.find_first_by_tag("failure") assert "hx" in fnode.toxml() - def test_assertion_binchars(self, testdir, run_and_parse): + def test_assertion_binchars(self, pytester: Pytester, run_and_parse) -> None: """This test did fail when the escaping wasn't strict.""" - testdir.makepyfile( + pytester.makepyfile( """ M1 = '\x01\x02\x03\x04' @@ -732,8 +787,10 @@ def test_str_compare(): print(dom.toxml()) @pytest.mark.parametrize("junit_logging", ["no", "system-out"]) - def test_pass_captures_stdout(self, testdir, run_and_parse, junit_logging): - testdir.makepyfile( + def test_pass_captures_stdout( + self, pytester: Pytester, run_and_parse, junit_logging + ) -> None: + pytester.makepyfile( """ def test_pass(): print('hello-stdout') @@ -753,8 +810,10 @@ def test_pass(): ), "'hello-stdout' should be in system-out" @pytest.mark.parametrize("junit_logging", ["no", "system-err"]) - def test_pass_captures_stderr(self, testdir, run_and_parse, junit_logging): - testdir.makepyfile( + def test_pass_captures_stderr( + self, pytester: Pytester, run_and_parse, junit_logging + ) -> None: + pytester.makepyfile( """ import sys def test_pass(): @@ -775,8 +834,10 @@ def test_pass(): ), "'hello-stderr' should be in system-err" @pytest.mark.parametrize("junit_logging", ["no", "system-out"]) - def test_setup_error_captures_stdout(self, testdir, run_and_parse, junit_logging): - testdir.makepyfile( + def test_setup_error_captures_stdout( + self, pytester: Pytester, run_and_parse, junit_logging + ) -> None: + pytester.makepyfile( """ import pytest @@ -802,8 +863,10 @@ def test_function(arg): ), "'hello-stdout' should be in system-out" @pytest.mark.parametrize("junit_logging", ["no", "system-err"]) - def test_setup_error_captures_stderr(self, testdir, run_and_parse, junit_logging): - testdir.makepyfile( + def test_setup_error_captures_stderr( + self, pytester: Pytester, run_and_parse, junit_logging + ) -> None: + pytester.makepyfile( """ import sys import pytest @@ -830,8 +893,10 @@ def test_function(arg): ), "'hello-stderr' should be in system-err" @pytest.mark.parametrize("junit_logging", ["no", "system-out"]) - def test_avoid_double_stdout(self, testdir, run_and_parse, junit_logging): - testdir.makepyfile( + def test_avoid_double_stdout( + self, pytester: Pytester, run_and_parse, junit_logging + ) -> None: + pytester.makepyfile( """ import sys import pytest @@ -858,7 +923,7 @@ def test_function(arg): assert "hello-stdout teardown" in systemout.toxml() -def test_mangle_test_address(): +def test_mangle_test_address() -> None: from _pytest.junitxml import mangle_test_address address = "::".join(["a/my.py.thing.py", "Class", "()", "method", "[a-1-::]"]) @@ -866,7 +931,7 @@ def test_mangle_test_address(): assert newnames == ["a.my.py.thing", "Class", "method", "[a-1-::]"] -def test_dont_configure_on_workers(tmpdir) -> None: +def test_dont_configure_on_workers(tmp_path: Path) -> None: gotten: List[object] = [] class FakeConfig: @@ -882,8 +947,8 @@ def getini(self, name): return "pytest" junitprefix = None - # XXX: shouldn't need tmpdir ? - xmlpath = str(tmpdir.join("junix.xml")) + # XXX: shouldn't need tmp_path ? + xmlpath = str(tmp_path.joinpath("junix.xml")) register = gotten.append fake_config = cast(Config, FakeConfig()) @@ -898,8 +963,10 @@ def getini(self, name): class TestNonPython: @parametrize_families - def test_summing_simple(self, testdir, run_and_parse, xunit_family): - testdir.makeconftest( + def test_summing_simple( + self, pytester: Pytester, run_and_parse, xunit_family + ) -> None: + pytester.makeconftest( """ import pytest def pytest_collect_file(path, parent): @@ -912,7 +979,7 @@ def repr_failure(self, excinfo): return "custom item runtest failed" """ ) - testdir.tmpdir.join("myfile.xyz").write("hello") + pytester.path.joinpath("myfile.xyz").write_text("hello") result, dom = run_and_parse(family=xunit_family) assert result.ret node = dom.find_first_by_tag("testsuite") @@ -925,9 +992,9 @@ def repr_failure(self, excinfo): @pytest.mark.parametrize("junit_logging", ["no", "system-out"]) -def test_nullbyte(testdir, junit_logging): +def test_nullbyte(pytester: Pytester, junit_logging) -> None: # A null byte can not occur in XML (see section 2.2 of the spec) - testdir.makepyfile( + pytester.makepyfile( """ import sys def test_print_nullbyte(): @@ -936,9 +1003,9 @@ def test_print_nullbyte(): assert False """ ) - xmlf = testdir.tmpdir.join("junit.xml") - testdir.runpytest("--junitxml=%s" % xmlf, "-o", "junit_logging=%s" % junit_logging) - text = xmlf.read() + xmlf = pytester.path.joinpath("junit.xml") + pytester.runpytest("--junitxml=%s" % xmlf, "-o", "junit_logging=%s" % junit_logging) + text = xmlf.read_text() assert "\x00" not in text if junit_logging == "system-out": assert "#x00" in text @@ -947,9 +1014,9 @@ def test_print_nullbyte(): @pytest.mark.parametrize("junit_logging", ["no", "system-out"]) -def test_nullbyte_replace(testdir, junit_logging): +def test_nullbyte_replace(pytester: Pytester, junit_logging) -> None: # Check if the null byte gets replaced - testdir.makepyfile( + pytester.makepyfile( """ import sys def test_print_nullbyte(): @@ -958,16 +1025,16 @@ def test_print_nullbyte(): assert False """ ) - xmlf = testdir.tmpdir.join("junit.xml") - testdir.runpytest("--junitxml=%s" % xmlf, "-o", "junit_logging=%s" % junit_logging) - text = xmlf.read() + xmlf = pytester.path.joinpath("junit.xml") + pytester.runpytest("--junitxml=%s" % xmlf, "-o", "junit_logging=%s" % junit_logging) + text = xmlf.read_text() if junit_logging == "system-out": assert "#x0" in text if junit_logging == "no": assert "#x0" not in text -def test_invalid_xml_escape(): +def test_invalid_xml_escape() -> None: # Test some more invalid xml chars, the full range should be # tested really but let's just test the edges of the ranges # instead. @@ -1003,52 +1070,52 @@ def test_invalid_xml_escape(): assert chr(i) == bin_xml_escape(chr(i)) -def test_logxml_path_expansion(tmpdir, monkeypatch): - home_tilde = py.path.local(os.path.expanduser("~")).join("test.xml") - xml_tilde = LogXML("~%stest.xml" % tmpdir.sep, None) - assert xml_tilde.logfile == home_tilde +def test_logxml_path_expansion(tmp_path: Path, monkeypatch: MonkeyPatch) -> None: + home_tilde = Path(os.path.expanduser("~")).joinpath("test.xml") + xml_tilde = LogXML(Path("~", "test.xml"), None) + assert xml_tilde.logfile == str(home_tilde) - monkeypatch.setenv("HOME", str(tmpdir)) + monkeypatch.setenv("HOME", str(tmp_path)) home_var = os.path.normpath(os.path.expandvars("$HOME/test.xml")) - xml_var = LogXML("$HOME%stest.xml" % tmpdir.sep, None) - assert xml_var.logfile == home_var + xml_var = LogXML(Path("$HOME", "test.xml"), None) + assert xml_var.logfile == str(home_var) -def test_logxml_changingdir(testdir): - testdir.makepyfile( +def test_logxml_changingdir(pytester: Pytester) -> None: + pytester.makepyfile( """ def test_func(): import os os.chdir("a") """ ) - testdir.tmpdir.mkdir("a") - result = testdir.runpytest("--junitxml=a/x.xml") + pytester.mkdir("a") + result = pytester.runpytest("--junitxml=a/x.xml") assert result.ret == 0 - assert testdir.tmpdir.join("a/x.xml").check() + assert pytester.path.joinpath("a/x.xml").exists() -def test_logxml_makedir(testdir): +def test_logxml_makedir(pytester: Pytester) -> None: """--junitxml should automatically create directories for the xml file""" - testdir.makepyfile( + pytester.makepyfile( """ def test_pass(): pass """ ) - result = testdir.runpytest("--junitxml=path/to/results.xml") + result = pytester.runpytest("--junitxml=path/to/results.xml") assert result.ret == 0 - assert testdir.tmpdir.join("path/to/results.xml").check() + assert pytester.path.joinpath("path/to/results.xml").exists() -def test_logxml_check_isdir(testdir): +def test_logxml_check_isdir(pytester: Pytester) -> None: """Give an error if --junit-xml is a directory (#2089)""" - result = testdir.runpytest("--junit-xml=.") + result = pytester.runpytest("--junit-xml=.") result.stderr.fnmatch_lines(["*--junitxml must be a filename*"]) -def test_escaped_parametrized_names_xml(testdir, run_and_parse): - testdir.makepyfile( +def test_escaped_parametrized_names_xml(pytester: Pytester, run_and_parse) -> None: + pytester.makepyfile( """\ import pytest @pytest.mark.parametrize('char', ["\\x00"]) @@ -1062,8 +1129,10 @@ def test_func(char): node.assert_attr(name="test_func[\\x00]") -def test_double_colon_split_function_issue469(testdir, run_and_parse): - testdir.makepyfile( +def test_double_colon_split_function_issue469( + pytester: Pytester, run_and_parse +) -> None: + pytester.makepyfile( """ import pytest @pytest.mark.parametrize('param', ["double::colon"]) @@ -1078,8 +1147,8 @@ def test_func(param): node.assert_attr(name="test_func[double::colon]") -def test_double_colon_split_method_issue469(testdir, run_and_parse): - testdir.makepyfile( +def test_double_colon_split_method_issue469(pytester: Pytester, run_and_parse) -> None: + pytester.makepyfile( """ import pytest class TestClass(object): @@ -1095,8 +1164,8 @@ def test_func(self, param): node.assert_attr(name="test_func[double::colon]") -def test_unicode_issue368(testdir) -> None: - path = testdir.tmpdir.join("test.xml") +def test_unicode_issue368(pytester: Pytester) -> None: + path = pytester.path.joinpath("test.xml") log = LogXML(str(path), None) ustr = "ВНИ!" @@ -1125,8 +1194,8 @@ class Report(BaseReport): log.pytest_sessionfinish() -def test_record_property(testdir, run_and_parse): - testdir.makepyfile( +def test_record_property(pytester: Pytester, run_and_parse) -> None: + pytester.makepyfile( """ import pytest @@ -1147,8 +1216,8 @@ def test_record(record_property, other): result.stdout.fnmatch_lines(["*= 1 passed in *"]) -def test_record_property_same_name(testdir, run_and_parse): - testdir.makepyfile( +def test_record_property_same_name(pytester: Pytester, run_and_parse) -> None: + pytester.makepyfile( """ def test_record_with_same_name(record_property): record_property("foo", "bar") @@ -1165,8 +1234,8 @@ def test_record_with_same_name(record_property): @pytest.mark.parametrize("fixture_name", ["record_property", "record_xml_attribute"]) -def test_record_fixtures_without_junitxml(testdir, fixture_name): - testdir.makepyfile( +def test_record_fixtures_without_junitxml(pytester: Pytester, fixture_name) -> None: + pytester.makepyfile( """ def test_record({fixture_name}): {fixture_name}("foo", "bar") @@ -1174,19 +1243,19 @@ def test_record({fixture_name}): fixture_name=fixture_name ) ) - result = testdir.runpytest() + result = pytester.runpytest() assert result.ret == 0 @pytest.mark.filterwarnings("default") -def test_record_attribute(testdir, run_and_parse): - testdir.makeini( +def test_record_attribute(pytester: Pytester, run_and_parse) -> None: + pytester.makeini( """ [pytest] junit_family = xunit1 """ ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -1209,15 +1278,17 @@ def test_record(record_xml_attribute, other): @pytest.mark.filterwarnings("default") @pytest.mark.parametrize("fixture_name", ["record_xml_attribute", "record_property"]) -def test_record_fixtures_xunit2(testdir, fixture_name, run_and_parse): +def test_record_fixtures_xunit2( + pytester: Pytester, fixture_name, run_and_parse +) -> None: """Ensure record_xml_attribute and record_property drop values when outside of legacy family.""" - testdir.makeini( + pytester.makeini( """ [pytest] junit_family = xunit2 """ ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -1246,13 +1317,15 @@ def test_record({fixture_name}, other): result.stdout.fnmatch_lines(expected_lines) -def test_random_report_log_xdist(testdir, monkeypatch, run_and_parse): +def test_random_report_log_xdist( + pytester: Pytester, monkeypatch: MonkeyPatch, run_and_parse +) -> None: """`xdist` calls pytest_runtest_logreport as they are executed by the workers, with nodes from several nodes overlapping, so junitxml must cope with that to produce correct reports (#1064).""" pytest.importorskip("xdist") monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False) - testdir.makepyfile( + pytester.makepyfile( """ import pytest, time @pytest.mark.parametrize('i', list(range(30))) @@ -1271,8 +1344,8 @@ def test_x(i): @parametrize_families -def test_root_testsuites_tag(testdir, run_and_parse, xunit_family): - testdir.makepyfile( +def test_root_testsuites_tag(pytester: Pytester, run_and_parse, xunit_family) -> None: + pytester.makepyfile( """ def test_x(): pass @@ -1285,8 +1358,8 @@ def test_x(): assert suite_node.tag == "testsuite" -def test_runs_twice(testdir, run_and_parse): - f = testdir.makepyfile( +def test_runs_twice(pytester: Pytester, run_and_parse) -> None: + f = pytester.makepyfile( """ def test_pass(): pass @@ -1299,10 +1372,12 @@ def test_pass(): assert first == second -def test_runs_twice_xdist(testdir, run_and_parse): +def test_runs_twice_xdist( + pytester: Pytester, monkeypatch: MonkeyPatch, run_and_parse +) -> None: pytest.importorskip("xdist") - testdir.monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD") - f = testdir.makepyfile( + monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD") + f = pytester.makepyfile( """ def test_pass(): pass @@ -1315,9 +1390,9 @@ def test_pass(): assert first == second -def test_fancy_items_regression(testdir, run_and_parse): +def test_fancy_items_regression(pytester: Pytester, run_and_parse) -> None: # issue 1259 - testdir.makeconftest( + pytester.makeconftest( """ import pytest class FunItem(pytest.Item): @@ -1341,7 +1416,7 @@ def pytest_collect_file(path, parent): """ ) - testdir.makepyfile( + pytester.makepyfile( """ def test_pass(): pass @@ -1368,8 +1443,8 @@ def test_pass(): @parametrize_families -def test_global_properties(testdir, xunit_family) -> None: - path = testdir.tmpdir.join("test_global_properties.xml") +def test_global_properties(pytester: Pytester, xunit_family) -> None: + path = pytester.path.joinpath("test_global_properties.xml") log = LogXML(str(path), None, family=xunit_family) class Report(BaseReport): @@ -1402,9 +1477,9 @@ class Report(BaseReport): assert actual == expected -def test_url_property(testdir) -> None: +def test_url_property(pytester: Pytester) -> None: test_url = "http://www.github.com/pytest-dev" - path = testdir.tmpdir.join("test_url_property.xml") + path = pytester.path.joinpath("test_url_property.xml") log = LogXML(str(path), None) class Report(BaseReport): @@ -1429,8 +1504,10 @@ class Report(BaseReport): @parametrize_families -def test_record_testsuite_property(testdir, run_and_parse, xunit_family): - testdir.makepyfile( +def test_record_testsuite_property( + pytester: Pytester, run_and_parse, xunit_family +) -> None: + pytester.makepyfile( """ def test_func1(record_testsuite_property): record_testsuite_property("stats", "all good") @@ -1449,27 +1526,27 @@ def test_func2(record_testsuite_property): p2_node.assert_attr(name="stats", value="10") -def test_record_testsuite_property_junit_disabled(testdir): - testdir.makepyfile( +def test_record_testsuite_property_junit_disabled(pytester: Pytester) -> None: + pytester.makepyfile( """ def test_func1(record_testsuite_property): record_testsuite_property("stats", "all good") """ ) - result = testdir.runpytest() + result = pytester.runpytest() assert result.ret == 0 @pytest.mark.parametrize("junit", [True, False]) -def test_record_testsuite_property_type_checking(testdir, junit): - testdir.makepyfile( +def test_record_testsuite_property_type_checking(pytester: Pytester, junit) -> None: + pytester.makepyfile( """ def test_func1(record_testsuite_property): record_testsuite_property(1, 2) """ ) args = ("--junitxml=tests.xml",) if junit else () - result = testdir.runpytest(*args) + result = pytester.runpytest(*args) assert result.ret == 1 result.stdout.fnmatch_lines( ["*TypeError: name parameter needs to be a string, but int given"] @@ -1478,9 +1555,11 @@ def test_func1(record_testsuite_property): @pytest.mark.parametrize("suite_name", ["my_suite", ""]) @parametrize_families -def test_set_suite_name(testdir, suite_name, run_and_parse, xunit_family): +def test_set_suite_name( + pytester: Pytester, suite_name, run_and_parse, xunit_family +) -> None: if suite_name: - testdir.makeini( + pytester.makeini( """ [pytest] junit_suite_name={suite_name} @@ -1492,7 +1571,7 @@ def test_set_suite_name(testdir, suite_name, run_and_parse, xunit_family): expected = suite_name else: expected = "pytest" - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -1506,8 +1585,8 @@ def test_func(): node.assert_attr(name=expected) -def test_escaped_skipreason_issue3533(testdir, run_and_parse): - testdir.makepyfile( +def test_escaped_skipreason_issue3533(pytester: Pytester, run_and_parse) -> None: + pytester.makepyfile( """ import pytest @pytest.mark.skip(reason='1 <> 2') @@ -1524,9 +1603,9 @@ def test_skip(): @parametrize_families def test_logging_passing_tests_disabled_does_not_log_test_output( - testdir, run_and_parse, xunit_family -): - testdir.makeini( + pytester: Pytester, run_and_parse, xunit_family +) -> None: + pytester.makeini( """ [pytest] junit_log_passing_tests=False @@ -1536,7 +1615,7 @@ def test_logging_passing_tests_disabled_does_not_log_test_output( family=xunit_family ) ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest import logging @@ -1558,9 +1637,9 @@ def test_func(): @parametrize_families @pytest.mark.parametrize("junit_logging", ["no", "system-out", "system-err"]) def test_logging_passing_tests_disabled_logs_output_for_failing_test_issue5430( - testdir, junit_logging, run_and_parse, xunit_family -): - testdir.makeini( + pytester: Pytester, junit_logging, run_and_parse, xunit_family +) -> None: + pytester.makeini( """ [pytest] junit_log_passing_tests=False @@ -1569,7 +1648,7 @@ def test_logging_passing_tests_disabled_logs_output_for_failing_test_issue5430( family=xunit_family ) ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest import logging diff --git a/testing/test_link_resolve.py b/testing/test_link_resolve.py index 7eaf4124796..60a86ada36e 100644 --- a/testing/test_link_resolve.py +++ b/testing/test_link_resolve.py @@ -3,15 +3,14 @@ import sys import textwrap from contextlib import contextmanager +from pathlib import Path from string import ascii_lowercase -import py.path - -from _pytest import pytester +from _pytest.pytester import Pytester @contextmanager -def subst_path_windows(filename): +def subst_path_windows(filepath: Path): for c in ascii_lowercase[7:]: # Create a subst drive from H-Z. c += ":" if not os.path.exists(c): @@ -20,14 +19,14 @@ def subst_path_windows(filename): else: raise AssertionError("Unable to find suitable drive letter for subst.") - directory = filename.dirpath() - basename = filename.basename + directory = filepath.parent + basename = filepath.name args = ["subst", drive, str(directory)] subprocess.check_call(args) assert os.path.exists(drive) try: - filename = py.path.local(drive) / basename + filename = Path(drive, os.sep, basename) yield filename finally: args = ["subst", "/D", drive] @@ -35,9 +34,9 @@ def subst_path_windows(filename): @contextmanager -def subst_path_linux(filename): - directory = filename.dirpath() - basename = filename.basename +def subst_path_linux(filepath: Path): + directory = filepath.parent + basename = filepath.name target = directory / ".." / "sub2" os.symlink(str(directory), str(target), target_is_directory=True) @@ -49,11 +48,11 @@ def subst_path_linux(filename): pass -def test_link_resolve(testdir: pytester.Testdir) -> None: +def test_link_resolve(pytester: Pytester) -> None: """See: https://github.com/pytest-dev/pytest/issues/5965.""" - sub1 = testdir.mkpydir("sub1") - p = sub1.join("test_foo.py") - p.write( + sub1 = pytester.mkpydir("sub1") + p = sub1.joinpath("test_foo.py") + p.write_text( textwrap.dedent( """ import pytest @@ -68,7 +67,7 @@ def test_foo(): subst = subst_path_windows with subst(p) as subst_p: - result = testdir.runpytest(str(subst_p), "-v") + result = pytester.runpytest(str(subst_p), "-v") # i.e.: Make sure that the error is reported as a relative path, not as a # resolved path. # See: https://github.com/pytest-dev/pytest/issues/5965 diff --git a/testing/test_main.py b/testing/test_main.py index f45607abc30..2ed111895cd 100644 --- a/testing/test_main.py +++ b/testing/test_main.py @@ -10,7 +10,6 @@ from _pytest.main import resolve_collection_argument from _pytest.main import validate_basetemp from _pytest.pytester import Pytester -from _pytest.pytester import Testdir @pytest.mark.parametrize( @@ -21,9 +20,9 @@ pytest.param((False, SystemExit)), ), ) -def test_wrap_session_notify_exception(ret_exc, testdir): +def test_wrap_session_notify_exception(ret_exc, pytester: Pytester) -> None: returncode, exc = ret_exc - c1 = testdir.makeconftest( + c1 = pytester.makeconftest( """ import pytest @@ -38,7 +37,7 @@ def pytest_internalerror(excrepr, excinfo): returncode=returncode, exc=exc.__name__ ) ) - result = testdir.runpytest() + result = pytester.runpytest() if returncode: assert result.ret == returncode else: @@ -65,9 +64,9 @@ def pytest_internalerror(excrepr, excinfo): @pytest.mark.parametrize("returncode", (None, 42)) def test_wrap_session_exit_sessionfinish( - returncode: Optional[int], testdir: Testdir + returncode: Optional[int], pytester: Pytester ) -> None: - testdir.makeconftest( + pytester.makeconftest( """ import pytest def pytest_sessionfinish(): @@ -76,7 +75,7 @@ def pytest_sessionfinish(): returncode=returncode ) ) - result = testdir.runpytest() + result = pytester.runpytest() if returncode: assert result.ret == returncode else: @@ -101,8 +100,8 @@ def test_validate_basetemp_fails(tmp_path, basetemp, monkeypatch): validate_basetemp(basetemp) -def test_validate_basetemp_integration(testdir): - result = testdir.runpytest("--basetemp=.") +def test_validate_basetemp_integration(pytester: Pytester) -> None: + result = pytester.runpytest("--basetemp=.") result.stderr.fnmatch_lines("*basetemp must not be*") @@ -203,14 +202,14 @@ def test_absolute_paths_are_resolved_correctly(self, invocation_path: Path) -> N ) == (Path(os.path.abspath("src")), []) -def test_module_full_path_without_drive(testdir): +def test_module_full_path_without_drive(pytester: Pytester) -> None: """Collect and run test using full path except for the drive letter (#7628). Passing a full path without a drive letter would trigger a bug in py.path.local where it would keep the full path without the drive letter around, instead of resolving to the full path, resulting in fixtures node ids not matching against test node ids correctly. """ - testdir.makepyfile( + pytester.makepyfile( **{ "project/conftest.py": """ import pytest @@ -220,7 +219,7 @@ def fix(): return 1 } ) - testdir.makepyfile( + pytester.makepyfile( **{ "project/tests/dummy_test.py": """ def test(fix): @@ -228,12 +227,12 @@ def test(fix): """ } ) - fn = testdir.tmpdir.join("project/tests/dummy_test.py") - assert fn.isfile() + fn = pytester.path.joinpath("project/tests/dummy_test.py") + assert fn.is_file() drive, path = os.path.splitdrive(str(fn)) - result = testdir.runpytest(path, "-v") + result = pytester.runpytest(path, "-v") result.stdout.fnmatch_lines( [ os.path.join("project", "tests", "dummy_test.py") + "::test PASSED *", diff --git a/testing/test_parseopt.py b/testing/test_parseopt.py index a124009c401..c33337b67b3 100644 --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -315,7 +315,7 @@ def test_argcomplete(pytester: Pytester, monkeypatch: MonkeyPatch) -> None: shlex.quote(sys.executable) ) ) - # alternative would be extended Testdir.{run(),_run(),popen()} to be able + # alternative would be extended Pytester.{run(),_run(),popen()} to be able # to handle a keyword argument env that replaces os.environ in popen or # extends the copy, advantage: could not forget to restore monkeypatch.setenv("_ARGCOMPLETE", "1") diff --git a/testing/test_pytester.py b/testing/test_pytester.py index f2e8dd5a36a..a9ba1a046f1 100644 --- a/testing/test_pytester.py +++ b/testing/test_pytester.py @@ -2,26 +2,26 @@ import subprocess import sys import time +from pathlib import Path +from types import ModuleType from typing import List -import py.path - -import _pytest.pytester as pytester +import _pytest.pytester as pytester_mod import pytest from _pytest.config import ExitCode from _pytest.config import PytestPluginManager +from _pytest.monkeypatch import MonkeyPatch from _pytest.pytester import CwdSnapshot from _pytest.pytester import HookRecorder from _pytest.pytester import LineMatcher from _pytest.pytester import Pytester from _pytest.pytester import SysModulesSnapshot from _pytest.pytester import SysPathsSnapshot -from _pytest.pytester import Testdir -def test_make_hook_recorder(testdir) -> None: - item = testdir.getitem("def test_func(): pass") - recorder = testdir.make_hook_recorder(item.config.pluginmanager) +def test_make_hook_recorder(pytester: Pytester) -> None: + item = pytester.getitem("def test_func(): pass") + recorder = pytester.make_hook_recorder(item.config.pluginmanager) assert not recorder.getfailures() # (The silly condition is to fool mypy that the code below this is reachable) @@ -35,11 +35,11 @@ class rep: skipped = False when = "call" - recorder.hook.pytest_runtest_logreport(report=rep) + recorder.hook.pytest_runtest_logreport(report=rep) # type: ignore[attr-defined] failures = recorder.getfailures() - assert failures == [rep] + assert failures == [rep] # type: ignore[comparison-overlap] failures = recorder.getfailures() - assert failures == [rep] + assert failures == [rep] # type: ignore[comparison-overlap] class rep2: excinfo = None @@ -50,14 +50,14 @@ class rep2: rep2.passed = False rep2.skipped = True - recorder.hook.pytest_runtest_logreport(report=rep2) + recorder.hook.pytest_runtest_logreport(report=rep2) # type: ignore[attr-defined] - modcol = testdir.getmodulecol("") + modcol = pytester.getmodulecol("") rep3 = modcol.config.hook.pytest_make_collect_report(collector=modcol) rep3.passed = False rep3.failed = True rep3.skipped = False - recorder.hook.pytest_collectreport(report=rep3) + recorder.hook.pytest_collectreport(report=rep3) # type: ignore[attr-defined] passed, skipped, failed = recorder.listoutcomes() assert not passed and skipped and failed @@ -68,55 +68,55 @@ class rep2: assert numfailed == 1 assert len(recorder.getfailedcollections()) == 1 - recorder.unregister() + recorder.unregister() # type: ignore[attr-defined] recorder.clear() - recorder.hook.pytest_runtest_logreport(report=rep3) + recorder.hook.pytest_runtest_logreport(report=rep3) # type: ignore[attr-defined] pytest.raises(ValueError, recorder.getfailures) -def test_parseconfig(testdir) -> None: - config1 = testdir.parseconfig() - config2 = testdir.parseconfig() +def test_parseconfig(pytester: Pytester) -> None: + config1 = pytester.parseconfig() + config2 = pytester.parseconfig() assert config2 is not config1 -def test_testdir_runs_with_plugin(testdir) -> None: - testdir.makepyfile( +def test_pytester_runs_with_plugin(pytester: Pytester) -> None: + pytester.makepyfile( """ pytest_plugins = "pytester" - def test_hello(testdir): + def test_hello(pytester): assert 1 """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.assert_outcomes(passed=1) -def test_testdir_with_doctest(testdir): - """Check that testdir can be used within doctests. +def test_pytester_with_doctest(pytester: Pytester): + """Check that pytester can be used within doctests. It used to use `request.function`, which is `None` with doctests.""" - testdir.makepyfile( + pytester.makepyfile( **{ "sub/t-doctest.py": """ ''' >>> import os - >>> testdir = getfixture("testdir") - >>> str(testdir.makepyfile("content")).replace(os.sep, '/') + >>> pytester = getfixture("pytester") + >>> str(pytester.makepyfile("content")).replace(os.sep, '/') '.../basetemp/sub.t-doctest0/sub.py' ''' """, "sub/__init__.py": "", } ) - result = testdir.runpytest( + result = pytester.runpytest( "-p", "pytester", "--doctest-modules", "sub/t-doctest.py" ) assert result.ret == 0 -def test_runresult_assertion_on_xfail(testdir) -> None: - testdir.makepyfile( +def test_runresult_assertion_on_xfail(pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -127,13 +127,13 @@ def test_potato(): assert False """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.assert_outcomes(xfailed=1) assert result.ret == 0 -def test_runresult_assertion_on_xpassed(testdir) -> None: - testdir.makepyfile( +def test_runresult_assertion_on_xpassed(pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -144,13 +144,13 @@ def test_potato(): assert True """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.assert_outcomes(xpassed=1) assert result.ret == 0 -def test_xpassed_with_strict_is_considered_a_failure(testdir) -> None: - testdir.makepyfile( +def test_xpassed_with_strict_is_considered_a_failure(pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @@ -161,7 +161,7 @@ def test_potato(): assert True """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.assert_outcomes(failed=1) assert result.ret != 0 @@ -202,28 +202,28 @@ def test_hookrecorder_basic(holder) -> None: assert call._name == "pytest_xyz_noarg" -def test_makepyfile_unicode(testdir) -> None: - testdir.makepyfile(chr(0xFFFD)) +def test_makepyfile_unicode(pytester: Pytester) -> None: + pytester.makepyfile(chr(0xFFFD)) -def test_makepyfile_utf8(testdir) -> None: +def test_makepyfile_utf8(pytester: Pytester) -> None: """Ensure makepyfile accepts utf-8 bytes as input (#2738)""" utf8_contents = """ def setup_function(function): mixed_encoding = 'São Paulo' """.encode() - p = testdir.makepyfile(utf8_contents) - assert "mixed_encoding = 'São Paulo'".encode() in p.read("rb") + p = pytester.makepyfile(utf8_contents) + assert "mixed_encoding = 'São Paulo'".encode() in p.read_bytes() class TestInlineRunModulesCleanup: - def test_inline_run_test_module_not_cleaned_up(self, testdir) -> None: - test_mod = testdir.makepyfile("def test_foo(): assert True") - result = testdir.inline_run(str(test_mod)) + def test_inline_run_test_module_not_cleaned_up(self, pytester: Pytester) -> None: + test_mod = pytester.makepyfile("def test_foo(): assert True") + result = pytester.inline_run(str(test_mod)) assert result.ret == ExitCode.OK # rewrite module, now test should fail if module was re-imported - test_mod.write("def test_foo(): assert False") - result2 = testdir.inline_run(str(test_mod)) + test_mod.write_text("def test_foo(): assert False") + result2 = pytester.inline_run(str(test_mod)) assert result2.ret == ExitCode.TESTS_FAILED def spy_factory(self): @@ -243,20 +243,20 @@ def restore(self): return SysModulesSnapshotSpy def test_inline_run_taking_and_restoring_a_sys_modules_snapshot( - self, testdir, monkeypatch + self, pytester: Pytester, monkeypatch: MonkeyPatch ) -> None: spy_factory = self.spy_factory() - monkeypatch.setattr(pytester, "SysModulesSnapshot", spy_factory) - testdir.syspathinsert() + monkeypatch.setattr(pytester_mod, "SysModulesSnapshot", spy_factory) + pytester.syspathinsert() original = dict(sys.modules) - testdir.makepyfile(import1="# you son of a silly person") - testdir.makepyfile(import2="# my hovercraft is full of eels") - test_mod = testdir.makepyfile( + pytester.makepyfile(import1="# you son of a silly person") + pytester.makepyfile(import2="# my hovercraft is full of eels") + test_mod = pytester.makepyfile( """ import import1 def test_foo(): import import2""" ) - testdir.inline_run(str(test_mod)) + pytester.inline_run(str(test_mod)) assert len(spy_factory.instances) == 1 spy = spy_factory.instances[0] assert spy._spy_restore_count == 1 @@ -264,55 +264,57 @@ def test_foo(): import import2""" assert all(sys.modules[x] is original[x] for x in sys.modules) def test_inline_run_sys_modules_snapshot_restore_preserving_modules( - self, testdir, monkeypatch + self, pytester: Pytester, monkeypatch: MonkeyPatch ) -> None: spy_factory = self.spy_factory() - monkeypatch.setattr(pytester, "SysModulesSnapshot", spy_factory) - test_mod = testdir.makepyfile("def test_foo(): pass") - testdir.inline_run(str(test_mod)) + monkeypatch.setattr(pytester_mod, "SysModulesSnapshot", spy_factory) + test_mod = pytester.makepyfile("def test_foo(): pass") + pytester.inline_run(str(test_mod)) spy = spy_factory.instances[0] assert not spy._spy_preserve("black_knight") assert spy._spy_preserve("zope") assert spy._spy_preserve("zope.interface") assert spy._spy_preserve("zopelicious") - def test_external_test_module_imports_not_cleaned_up(self, testdir) -> None: - testdir.syspathinsert() - testdir.makepyfile(imported="data = 'you son of a silly person'") + def test_external_test_module_imports_not_cleaned_up( + self, pytester: Pytester + ) -> None: + pytester.syspathinsert() + pytester.makepyfile(imported="data = 'you son of a silly person'") import imported - test_mod = testdir.makepyfile( + test_mod = pytester.makepyfile( """ def test_foo(): import imported imported.data = 42""" ) - testdir.inline_run(str(test_mod)) + pytester.inline_run(str(test_mod)) assert imported.data == 42 -def test_assert_outcomes_after_pytest_error(testdir) -> None: - testdir.makepyfile("def test_foo(): assert True") +def test_assert_outcomes_after_pytest_error(pytester: Pytester) -> None: + pytester.makepyfile("def test_foo(): assert True") - result = testdir.runpytest("--unexpected-argument") + result = pytester.runpytest("--unexpected-argument") with pytest.raises(ValueError, match="Pytest terminal summary report not found"): result.assert_outcomes(passed=0) -def test_cwd_snapshot(testdir: Testdir) -> None: - tmpdir = testdir.tmpdir - foo = tmpdir.ensure("foo", dir=1) - bar = tmpdir.ensure("bar", dir=1) - foo.chdir() +def test_cwd_snapshot(pytester: Pytester) -> None: + foo = pytester.mkdir("foo") + bar = pytester.mkdir("bar") + os.chdir(foo) snapshot = CwdSnapshot() - bar.chdir() - assert py.path.local() == bar + os.chdir(bar) + assert Path().absolute() == bar snapshot.restore() - assert py.path.local() == foo + assert Path().absolute() == foo class TestSysModulesSnapshot: key = "my-test-module" + mod = ModuleType("something") def test_remove_added(self) -> None: original = dict(sys.modules) @@ -323,9 +325,9 @@ def test_remove_added(self) -> None: snapshot.restore() assert sys.modules == original - def test_add_removed(self, monkeypatch) -> None: + def test_add_removed(self, monkeypatch: MonkeyPatch) -> None: assert self.key not in sys.modules - monkeypatch.setitem(sys.modules, self.key, "something") + monkeypatch.setitem(sys.modules, self.key, self.mod) assert self.key in sys.modules original = dict(sys.modules) snapshot = SysModulesSnapshot() @@ -334,9 +336,9 @@ def test_add_removed(self, monkeypatch) -> None: snapshot.restore() assert sys.modules == original - def test_restore_reloaded(self, monkeypatch) -> None: + def test_restore_reloaded(self, monkeypatch: MonkeyPatch) -> None: assert self.key not in sys.modules - monkeypatch.setitem(sys.modules, self.key, "something") + monkeypatch.setitem(sys.modules, self.key, self.mod) assert self.key in sys.modules original = dict(sys.modules) snapshot = SysModulesSnapshot() @@ -344,11 +346,12 @@ def test_restore_reloaded(self, monkeypatch) -> None: snapshot.restore() assert sys.modules == original - def test_preserve_modules(self, monkeypatch) -> None: + def test_preserve_modules(self, monkeypatch: MonkeyPatch) -> None: key = [self.key + str(i) for i in range(3)] assert not any(k in sys.modules for k in key) for i, k in enumerate(key): - monkeypatch.setitem(sys.modules, k, "something" + str(i)) + mod = ModuleType("something" + str(i)) + monkeypatch.setitem(sys.modules, k, mod) original = dict(sys.modules) def preserve(name): @@ -361,7 +364,7 @@ def preserve(name): snapshot.restore() assert sys.modules == original - def test_preserve_container(self, monkeypatch) -> None: + def test_preserve_container(self, monkeypatch: MonkeyPatch) -> None: original = dict(sys.modules) assert self.key not in original replacement = dict(sys.modules) @@ -381,7 +384,7 @@ class TestSysPathsSnapshot: def path(n: int) -> str: return "my-dirty-little-secret-" + str(n) - def test_restore(self, monkeypatch, path_type) -> None: + def test_restore(self, monkeypatch: MonkeyPatch, path_type) -> None: other_path_type = self.other_path[path_type] for i in range(10): assert self.path(i) not in getattr(sys, path_type) @@ -404,7 +407,7 @@ def test_restore(self, monkeypatch, path_type) -> None: assert getattr(sys, path_type) == original assert getattr(sys, other_path_type) == original_other - def test_preserve_container(self, monkeypatch, path_type) -> None: + def test_preserve_container(self, monkeypatch: MonkeyPatch, path_type) -> None: other_path_type = self.other_path[path_type] original_data = list(getattr(sys, path_type)) original_other = getattr(sys, other_path_type) @@ -419,49 +422,47 @@ def test_preserve_container(self, monkeypatch, path_type) -> None: assert getattr(sys, other_path_type) == original_other_data -def test_testdir_subprocess(testdir) -> None: - testfile = testdir.makepyfile("def test_one(): pass") - assert testdir.runpytest_subprocess(testfile).ret == 0 +def test_pytester_subprocess(pytester: Pytester) -> None: + testfile = pytester.makepyfile("def test_one(): pass") + assert pytester.runpytest_subprocess(testfile).ret == 0 -def test_testdir_subprocess_via_runpytest_arg(testdir) -> None: - testfile = testdir.makepyfile( +def test_pytester_subprocess_via_runpytest_arg(pytester: Pytester) -> None: + testfile = pytester.makepyfile( """ - def test_testdir_subprocess(testdir): + def test_pytester_subprocess(pytester): import os - testfile = testdir.makepyfile( + testfile = pytester.makepyfile( \""" import os def test_one(): assert {} != os.getpid() \""".format(os.getpid()) ) - assert testdir.runpytest(testfile).ret == 0 + assert pytester.runpytest(testfile).ret == 0 """ ) - result = testdir.runpytest_subprocess( - "-p", "pytester", "--runpytest", "subprocess", testfile - ) + result = pytester.runpytest("-p", "pytester", "--runpytest", "subprocess", testfile) assert result.ret == 0 -def test_unicode_args(testdir) -> None: - result = testdir.runpytest("-k", "אבג") +def test_unicode_args(pytester: Pytester) -> None: + result = pytester.runpytest("-k", "אבג") assert result.ret == ExitCode.NO_TESTS_COLLECTED -def test_testdir_run_no_timeout(testdir) -> None: - testfile = testdir.makepyfile("def test_no_timeout(): pass") - assert testdir.runpytest_subprocess(testfile).ret == ExitCode.OK +def test_pytester_run_no_timeout(pytester: Pytester) -> None: + testfile = pytester.makepyfile("def test_no_timeout(): pass") + assert pytester.runpytest_subprocess(testfile).ret == ExitCode.OK -def test_testdir_run_with_timeout(testdir) -> None: - testfile = testdir.makepyfile("def test_no_timeout(): pass") +def test_pytester_run_with_timeout(pytester: Pytester) -> None: + testfile = pytester.makepyfile("def test_no_timeout(): pass") timeout = 120 start = time.time() - result = testdir.runpytest_subprocess(testfile, timeout=timeout) + result = pytester.runpytest_subprocess(testfile, timeout=timeout) end = time.time() duration = end - start @@ -469,16 +470,16 @@ def test_testdir_run_with_timeout(testdir) -> None: assert duration < timeout -def test_testdir_run_timeout_expires(testdir) -> None: - testfile = testdir.makepyfile( +def test_pytester_run_timeout_expires(pytester: Pytester) -> None: + testfile = pytester.makepyfile( """ import time def test_timeout(): time.sleep(10)""" ) - with pytest.raises(testdir.TimeoutExpired): - testdir.runpytest_subprocess(testfile, timeout=1) + with pytest.raises(pytester.TimeoutExpired): + pytester.runpytest_subprocess(testfile, timeout=1) def test_linematcher_with_nonlist() -> None: @@ -533,7 +534,7 @@ def test_linematcher_match_failure() -> None: ] -def test_linematcher_consecutive(): +def test_linematcher_consecutive() -> None: lm = LineMatcher(["1", "", "2"]) with pytest.raises(pytest.fail.Exception) as excinfo: lm.fnmatch_lines(["1", "2"], consecutive=True) @@ -554,7 +555,7 @@ def test_linematcher_consecutive(): @pytest.mark.parametrize("function", ["no_fnmatch_line", "no_re_match_line"]) -def test_linematcher_no_matching(function) -> None: +def test_linematcher_no_matching(function: str) -> None: if function == "no_fnmatch_line": good_pattern = "*.py OK*" bad_pattern = "*X.py OK*" @@ -615,7 +616,7 @@ def test_linematcher_string_api() -> None: assert str(lm) == "foo\nbar" -def test_pytester_addopts_before_testdir(request, monkeypatch) -> None: +def test_pytester_addopts_before_testdir(request, monkeypatch: MonkeyPatch) -> None: orig = os.environ.get("PYTEST_ADDOPTS", None) monkeypatch.setenv("PYTEST_ADDOPTS", "--orig-unused") testdir = request.getfixturevalue("testdir") @@ -626,9 +627,9 @@ def test_pytester_addopts_before_testdir(request, monkeypatch) -> None: assert os.environ.get("PYTEST_ADDOPTS") == orig -def test_run_stdin(testdir) -> None: - with pytest.raises(testdir.TimeoutExpired): - testdir.run( +def test_run_stdin(pytester: Pytester) -> None: + with pytest.raises(pytester.TimeoutExpired): + pytester.run( sys.executable, "-c", "import sys, time; time.sleep(1); print(sys.stdin.read())", @@ -636,8 +637,8 @@ def test_run_stdin(testdir) -> None: timeout=0.1, ) - with pytest.raises(testdir.TimeoutExpired): - result = testdir.run( + with pytest.raises(pytester.TimeoutExpired): + result = pytester.run( sys.executable, "-c", "import sys, time; time.sleep(1); print(sys.stdin.read())", @@ -645,7 +646,7 @@ def test_run_stdin(testdir) -> None: timeout=0.1, ) - result = testdir.run( + result = pytester.run( sys.executable, "-c", "import sys; print(sys.stdin.read())", @@ -656,8 +657,8 @@ def test_run_stdin(testdir) -> None: assert result.ret == 0 -def test_popen_stdin_pipe(testdir) -> None: - proc = testdir.popen( +def test_popen_stdin_pipe(pytester: Pytester) -> None: + proc = pytester.popen( [sys.executable, "-c", "import sys; print(sys.stdin.read())"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, @@ -670,8 +671,8 @@ def test_popen_stdin_pipe(testdir) -> None: assert proc.returncode == 0 -def test_popen_stdin_bytes(testdir) -> None: - proc = testdir.popen( +def test_popen_stdin_bytes(pytester: Pytester) -> None: + proc = pytester.popen( [sys.executable, "-c", "import sys; print(sys.stdin.read())"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, @@ -683,18 +684,18 @@ def test_popen_stdin_bytes(testdir) -> None: assert proc.returncode == 0 -def test_popen_default_stdin_stderr_and_stdin_None(testdir) -> None: +def test_popen_default_stdin_stderr_and_stdin_None(pytester: Pytester) -> None: # stdout, stderr default to pipes, # stdin can be None to not close the pipe, avoiding # "ValueError: flush of closed file" with `communicate()`. # # Wraps the test to make it not hang when run with "-s". - p1 = testdir.makepyfile( + p1 = pytester.makepyfile( ''' import sys - def test_inner(testdir): - p1 = testdir.makepyfile( + def test_inner(pytester): + p1 = pytester.makepyfile( """ import sys print(sys.stdin.read()) # empty @@ -702,14 +703,14 @@ def test_inner(testdir): sys.stderr.write('stderr') """ ) - proc = testdir.popen([sys.executable, str(p1)], stdin=None) + proc = pytester.popen([sys.executable, str(p1)], stdin=None) stdout, stderr = proc.communicate(b"ignored") assert stdout.splitlines() == [b"", b"stdout"] assert stderr.splitlines() == [b"stderr"] assert proc.returncode == 0 ''' ) - result = testdir.runpytest("-p", "pytester", str(p1)) + result = pytester.runpytest("-p", "pytester", str(p1)) assert result.ret == 0 @@ -740,22 +741,22 @@ def test_run_result_repr() -> None: errlines = ["some", "nasty", "errors", "happened"] # known exit code - r = pytester.RunResult(1, outlines, errlines, duration=0.5) + r = pytester_mod.RunResult(1, outlines, errlines, duration=0.5) assert ( repr(r) == "" ) # unknown exit code: just the number - r = pytester.RunResult(99, outlines, errlines, duration=0.5) + r = pytester_mod.RunResult(99, outlines, errlines, duration=0.5) assert ( repr(r) == "" ) -def test_testdir_outcomes_with_multiple_errors(testdir): - p1 = testdir.makepyfile( +def test_pytester_outcomes_with_multiple_errors(pytester: Pytester) -> None: + p1 = pytester.makepyfile( """ import pytest @@ -770,7 +771,7 @@ def test_error2(bad_fixture): pass """ ) - result = testdir.runpytest(str(p1)) + result = pytester.runpytest(str(p1)) result.assert_outcomes(errors=2) assert result.parseoutcomes() == {"errors": 2} @@ -784,7 +785,7 @@ def test_parse_summary_line_always_plural(): "======= 1 failed, 1 passed, 1 warning, 1 error in 0.13s ====", "done.", ] - assert pytester.RunResult.parse_summary_nouns(lines) == { + assert pytester_mod.RunResult.parse_summary_nouns(lines) == { "errors": 1, "failed": 1, "passed": 1, @@ -797,7 +798,7 @@ def test_parse_summary_line_always_plural(): "======= 1 failed, 1 passed, 2 warnings, 2 errors in 0.13s ====", "done.", ] - assert pytester.RunResult.parse_summary_nouns(lines) == { + assert pytester_mod.RunResult.parse_summary_nouns(lines) == { "errors": 2, "failed": 1, "passed": 1, @@ -805,10 +806,10 @@ def test_parse_summary_line_always_plural(): } -def test_makefile_joins_absolute_path(testdir: Testdir) -> None: - absfile = testdir.tmpdir / "absfile" - p1 = testdir.makepyfile(**{str(absfile): ""}) - assert str(p1) == str(testdir.tmpdir / "absfile.py") +def test_makefile_joins_absolute_path(pytester: Pytester) -> None: + absfile = pytester.path / "absfile" + p1 = pytester.makepyfile(**{str(absfile): ""}) + assert str(p1) == str(pytester.path / "absfile.py") def test_testtmproot(testdir): diff --git a/testing/test_unittest.py b/testing/test_unittest.py index 8b00cb826ac..feee09286c2 100644 --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -4,11 +4,12 @@ import pytest from _pytest.config import ExitCode -from _pytest.pytester import Testdir +from _pytest.monkeypatch import MonkeyPatch +from _pytest.pytester import Pytester -def test_simple_unittest(testdir): - testpath = testdir.makepyfile( +def test_simple_unittest(pytester: Pytester) -> None: + testpath = pytester.makepyfile( """ import unittest class MyTestCase(unittest.TestCase): @@ -18,13 +19,13 @@ def test_failing(self): self.assertEqual('foo', 'bar') """ ) - reprec = testdir.inline_run(testpath) + reprec = pytester.inline_run(testpath) assert reprec.matchreport("testpassing").passed assert reprec.matchreport("test_failing").failed -def test_runTest_method(testdir): - testdir.makepyfile( +def test_runTest_method(pytester: Pytester) -> None: + pytester.makepyfile( """ import unittest class MyTestCaseWithRunTest(unittest.TestCase): @@ -37,7 +38,7 @@ def test_something(self): pass """ ) - result = testdir.runpytest("-v") + result = pytester.runpytest("-v") result.stdout.fnmatch_lines( """ *MyTestCaseWithRunTest::runTest* @@ -47,8 +48,8 @@ def test_something(self): ) -def test_isclasscheck_issue53(testdir): - testpath = testdir.makepyfile( +def test_isclasscheck_issue53(pytester: Pytester) -> None: + testpath = pytester.makepyfile( """ import unittest class _E(object): @@ -57,12 +58,12 @@ def __getattr__(self, tag): E = _E() """ ) - result = testdir.runpytest(testpath) + result = pytester.runpytest(testpath) assert result.ret == ExitCode.NO_TESTS_COLLECTED -def test_setup(testdir): - testpath = testdir.makepyfile( +def test_setup(pytester: Pytester) -> None: + testpath = pytester.makepyfile( """ import unittest class MyTestCase(unittest.TestCase): @@ -78,14 +79,14 @@ def teardown_method(self, method): """ ) - reprec = testdir.inline_run("-s", testpath) + reprec = pytester.inline_run("-s", testpath) assert reprec.matchreport("test_both", when="call").passed rep = reprec.matchreport("test_both", when="teardown") assert rep.failed and "42" in str(rep.longrepr) -def test_setUpModule(testdir): - testpath = testdir.makepyfile( +def test_setUpModule(pytester: Pytester) -> None: + testpath = pytester.makepyfile( """ values = [] @@ -102,12 +103,12 @@ def test_world(): assert values == [1] """ ) - result = testdir.runpytest(testpath) + result = pytester.runpytest(testpath) result.stdout.fnmatch_lines(["*2 passed*"]) -def test_setUpModule_failing_no_teardown(testdir): - testpath = testdir.makepyfile( +def test_setUpModule_failing_no_teardown(pytester: Pytester) -> None: + testpath = pytester.makepyfile( """ values = [] @@ -121,14 +122,14 @@ def test_hello(): pass """ ) - reprec = testdir.inline_run(testpath) + reprec = pytester.inline_run(testpath) reprec.assertoutcome(passed=0, failed=1) call = reprec.getcalls("pytest_runtest_setup")[0] assert not call.item.module.values -def test_new_instances(testdir): - testpath = testdir.makepyfile( +def test_new_instances(pytester: Pytester) -> None: + testpath = pytester.makepyfile( """ import unittest class MyTestCase(unittest.TestCase): @@ -138,13 +139,13 @@ def test_func2(self): assert not hasattr(self, 'x') """ ) - reprec = testdir.inline_run(testpath) + reprec = pytester.inline_run(testpath) reprec.assertoutcome(passed=2) -def test_function_item_obj_is_instance(testdir): +def test_function_item_obj_is_instance(pytester: Pytester) -> None: """item.obj should be a bound method on unittest.TestCase function items (#5390).""" - testdir.makeconftest( + pytester.makeconftest( """ def pytest_runtest_makereport(item, call): if call.when == 'call': @@ -152,7 +153,7 @@ def pytest_runtest_makereport(item, call): assert isinstance(item.obj.__self__, class_) """ ) - testdir.makepyfile( + pytester.makepyfile( """ import unittest @@ -161,12 +162,12 @@ def test_foo(self): pass """ ) - result = testdir.runpytest_inprocess() + result = pytester.runpytest_inprocess() result.stdout.fnmatch_lines(["* 1 passed in*"]) -def test_teardown(testdir): - testpath = testdir.makepyfile( +def test_teardown(pytester: Pytester) -> None: + testpath = pytester.makepyfile( """ import unittest class MyTestCase(unittest.TestCase): @@ -180,14 +181,14 @@ def test_check(self): self.assertEqual(MyTestCase.values, [None]) """ ) - reprec = testdir.inline_run(testpath) + reprec = pytester.inline_run(testpath) passed, skipped, failed = reprec.countoutcomes() assert failed == 0, failed assert passed == 2 assert passed + skipped + failed == 2 -def test_teardown_issue1649(testdir): +def test_teardown_issue1649(pytester: Pytester) -> None: """ Are TestCase objects cleaned up? Often unittest TestCase objects set attributes that are large and expensive during setUp. @@ -195,7 +196,7 @@ def test_teardown_issue1649(testdir): The TestCase will not be cleaned up if the test fails, because it would then exist in the stackframe. """ - testpath = testdir.makepyfile( + testpath = pytester.makepyfile( """ import unittest class TestCaseObjectsShouldBeCleanedUp(unittest.TestCase): @@ -206,14 +207,14 @@ def test_demo(self): """ ) - testdir.inline_run("-s", testpath) + pytester.inline_run("-s", testpath) gc.collect() for obj in gc.get_objects(): assert type(obj).__name__ != "TestCaseObjectsShouldBeCleanedUp" -def test_unittest_skip_issue148(testdir): - testpath = testdir.makepyfile( +def test_unittest_skip_issue148(pytester: Pytester) -> None: + testpath = pytester.makepyfile( """ import unittest @@ -229,12 +230,12 @@ def tearDownClass(self): xxx """ ) - reprec = testdir.inline_run(testpath) + reprec = pytester.inline_run(testpath) reprec.assertoutcome(skipped=1) -def test_method_and_teardown_failing_reporting(testdir): - testdir.makepyfile( +def test_method_and_teardown_failing_reporting(pytester: Pytester) -> None: + pytester.makepyfile( """ import unittest class TC(unittest.TestCase): @@ -244,7 +245,7 @@ def test_method(self): assert False, "down2" """ ) - result = testdir.runpytest("-s") + result = pytester.runpytest("-s") assert result.ret == 1 result.stdout.fnmatch_lines( [ @@ -257,8 +258,8 @@ def test_method(self): ) -def test_setup_failure_is_shown(testdir): - testdir.makepyfile( +def test_setup_failure_is_shown(pytester: Pytester) -> None: + pytester.makepyfile( """ import unittest import pytest @@ -270,14 +271,14 @@ def test_method(self): xyz """ ) - result = testdir.runpytest("-s") + result = pytester.runpytest("-s") assert result.ret == 1 result.stdout.fnmatch_lines(["*setUp*", "*assert 0*down1*", "*1 failed*"]) result.stdout.no_fnmatch_line("*never42*") -def test_setup_setUpClass(testdir): - testpath = testdir.makepyfile( +def test_setup_setUpClass(pytester: Pytester) -> None: + testpath = pytester.makepyfile( """ import unittest import pytest @@ -297,12 +298,12 @@ def test_teareddown(): assert MyTestCase.x == 0 """ ) - reprec = testdir.inline_run(testpath) + reprec = pytester.inline_run(testpath) reprec.assertoutcome(passed=3) -def test_setup_class(testdir): - testpath = testdir.makepyfile( +def test_setup_class(pytester: Pytester) -> None: + testpath = pytester.makepyfile( """ import unittest import pytest @@ -320,13 +321,13 @@ def test_teareddown(): assert MyTestCase.x == 0 """ ) - reprec = testdir.inline_run(testpath) + reprec = pytester.inline_run(testpath) reprec.assertoutcome(passed=3) @pytest.mark.parametrize("type", ["Error", "Failure"]) -def test_testcase_adderrorandfailure_defers(testdir, type): - testdir.makepyfile( +def test_testcase_adderrorandfailure_defers(pytester: Pytester, type: str) -> None: + pytester.makepyfile( """ from unittest import TestCase import pytest @@ -344,13 +345,13 @@ def test_hello(self): """ % (type, type) ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.no_fnmatch_line("*should not raise*") @pytest.mark.parametrize("type", ["Error", "Failure"]) -def test_testcase_custom_exception_info(testdir, type): - testdir.makepyfile( +def test_testcase_custom_exception_info(pytester: Pytester, type: str) -> None: + pytester.makepyfile( """ from unittest import TestCase import py, pytest @@ -375,7 +376,7 @@ def test_hello(self): """ % locals() ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( [ "NOTE: Incompatible Exception Representation*", @@ -385,8 +386,10 @@ def test_hello(self): ) -def test_testcase_totally_incompatible_exception_info(testdir): - (item,) = testdir.getitems( +def test_testcase_totally_incompatible_exception_info(pytester: Pytester) -> None: + import _pytest.unittest + + (item,) = pytester.getitems( """ from unittest import TestCase class MyTestCase(TestCase): @@ -394,13 +397,15 @@ def test_hello(self): pass """ ) - item.addError(None, 42) - excinfo = item._excinfo.pop(0) - assert "ERROR: Unknown Incompatible" in str(excinfo.getrepr()) + assert isinstance(item, _pytest.unittest.TestCaseFunction) + item.addError(None, 42) # type: ignore[arg-type] + excinfo = item._excinfo + assert excinfo is not None + assert "ERROR: Unknown Incompatible" in str(excinfo.pop(0).getrepr()) -def test_module_level_pytestmark(testdir): - testpath = testdir.makepyfile( +def test_module_level_pytestmark(pytester: Pytester) -> None: + testpath = pytester.makepyfile( """ import unittest import pytest @@ -410,7 +415,7 @@ def test_func1(self): assert 0 """ ) - reprec = testdir.inline_run(testpath, "-s") + reprec = pytester.inline_run(testpath, "-s") reprec.assertoutcome(skipped=1) @@ -421,8 +426,8 @@ def setup_class(cls): # https://twistedmatrix.com/trac/ticket/9227 cls.ignore_unclosed_socket_warning = ("-W", "always") - def test_trial_testcase_runtest_not_collected(self, testdir): - testdir.makepyfile( + def test_trial_testcase_runtest_not_collected(self, pytester: Pytester) -> None: + pytester.makepyfile( """ from twisted.trial.unittest import TestCase @@ -431,9 +436,9 @@ def test_hello(self): pass """ ) - reprec = testdir.inline_run(*self.ignore_unclosed_socket_warning) + reprec = pytester.inline_run(*self.ignore_unclosed_socket_warning) reprec.assertoutcome(passed=1) - testdir.makepyfile( + pytester.makepyfile( """ from twisted.trial.unittest import TestCase @@ -442,11 +447,11 @@ def runTest(self): pass """ ) - reprec = testdir.inline_run(*self.ignore_unclosed_socket_warning) + reprec = pytester.inline_run(*self.ignore_unclosed_socket_warning) reprec.assertoutcome(passed=1) - def test_trial_exceptions_with_skips(self, testdir): - testdir.makepyfile( + def test_trial_exceptions_with_skips(self, pytester: Pytester) -> None: + pytester.makepyfile( """ from twisted.trial import unittest import pytest @@ -480,7 +485,7 @@ def test_method(self): pass """ ) - result = testdir.runpytest("-rxs", *self.ignore_unclosed_socket_warning) + result = pytester.runpytest("-rxs", *self.ignore_unclosed_socket_warning) result.stdout.fnmatch_lines_random( [ "*XFAIL*test_trial_todo*", @@ -495,8 +500,8 @@ def test_method(self): ) assert result.ret == 1 - def test_trial_error(self, testdir): - testdir.makepyfile( + def test_trial_error(self, pytester: Pytester) -> None: + pytester.makepyfile( """ from twisted.trial.unittest import TestCase from twisted.internet.defer import Deferred @@ -533,7 +538,7 @@ def f(_): # will crash both at test time and at teardown """ ) - result = testdir.runpytest("-vv", "-oconsole_output_style=classic") + result = pytester.runpytest("-vv", "-oconsole_output_style=classic") result.stdout.fnmatch_lines( [ "test_trial_error.py::TC::test_four FAILED", @@ -557,8 +562,8 @@ def f(_): ] ) - def test_trial_pdb(self, testdir): - p = testdir.makepyfile( + def test_trial_pdb(self, pytester: Pytester) -> None: + p = pytester.makepyfile( """ from twisted.trial import unittest import pytest @@ -567,12 +572,12 @@ def test_hello(self): assert 0, "hellopdb" """ ) - child = testdir.spawn_pytest(p) + child = pytester.spawn_pytest(str(p)) child.expect("hellopdb") child.sendeof() - def test_trial_testcase_skip_property(self, testdir): - testpath = testdir.makepyfile( + def test_trial_testcase_skip_property(self, pytester: Pytester) -> None: + testpath = pytester.makepyfile( """ from twisted.trial import unittest class MyTestCase(unittest.TestCase): @@ -581,11 +586,11 @@ def test_func(self): pass """ ) - reprec = testdir.inline_run(testpath, "-s") + reprec = pytester.inline_run(testpath, "-s") reprec.assertoutcome(skipped=1) - def test_trial_testfunction_skip_property(self, testdir): - testpath = testdir.makepyfile( + def test_trial_testfunction_skip_property(self, pytester: Pytester) -> None: + testpath = pytester.makepyfile( """ from twisted.trial import unittest class MyTestCase(unittest.TestCase): @@ -594,11 +599,11 @@ def test_func(self): test_func.skip = 'dont run' """ ) - reprec = testdir.inline_run(testpath, "-s") + reprec = pytester.inline_run(testpath, "-s") reprec.assertoutcome(skipped=1) - def test_trial_testcase_todo_property(self, testdir): - testpath = testdir.makepyfile( + def test_trial_testcase_todo_property(self, pytester: Pytester) -> None: + testpath = pytester.makepyfile( """ from twisted.trial import unittest class MyTestCase(unittest.TestCase): @@ -607,11 +612,11 @@ def test_func(self): assert 0 """ ) - reprec = testdir.inline_run(testpath, "-s") + reprec = pytester.inline_run(testpath, "-s") reprec.assertoutcome(skipped=1) - def test_trial_testfunction_todo_property(self, testdir): - testpath = testdir.makepyfile( + def test_trial_testfunction_todo_property(self, pytester: Pytester) -> None: + testpath = pytester.makepyfile( """ from twisted.trial import unittest class MyTestCase(unittest.TestCase): @@ -620,15 +625,15 @@ def test_func(self): test_func.todo = 'dont run' """ ) - reprec = testdir.inline_run( + reprec = pytester.inline_run( testpath, "-s", *self.ignore_unclosed_socket_warning ) reprec.assertoutcome(skipped=1) -def test_djangolike_testcase(testdir): +def test_djangolike_testcase(pytester: Pytester) -> None: # contributed from Morten Breekevold - testdir.makepyfile( + pytester.makepyfile( """ from unittest import TestCase, main @@ -671,7 +676,7 @@ def _post_teardown(self): print("_post_teardown()") """ ) - result = testdir.runpytest("-s") + result = pytester.runpytest("-s") assert result.ret == 0 result.stdout.fnmatch_lines( [ @@ -684,8 +689,8 @@ def _post_teardown(self): ) -def test_unittest_not_shown_in_traceback(testdir): - testdir.makepyfile( +def test_unittest_not_shown_in_traceback(pytester: Pytester) -> None: + pytester.makepyfile( """ import unittest class t(unittest.TestCase): @@ -694,12 +699,12 @@ def test_hello(self): self.assertEqual(x, 4) """ ) - res = testdir.runpytest() + res = pytester.runpytest() res.stdout.no_fnmatch_line("*failUnlessEqual*") -def test_unorderable_types(testdir): - testdir.makepyfile( +def test_unorderable_types(pytester: Pytester) -> None: + pytester.makepyfile( """ import unittest class TestJoinEmpty(unittest.TestCase): @@ -713,13 +718,13 @@ class Test(unittest.TestCase): TestFoo = make_test() """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.no_fnmatch_line("*TypeError*") assert result.ret == ExitCode.NO_TESTS_COLLECTED -def test_unittest_typerror_traceback(testdir): - testdir.makepyfile( +def test_unittest_typerror_traceback(pytester: Pytester) -> None: + pytester.makepyfile( """ import unittest class TestJoinEmpty(unittest.TestCase): @@ -727,14 +732,16 @@ def test_hello(self, arg1): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() assert "TypeError" in result.stdout.str() assert result.ret == 1 @pytest.mark.parametrize("runner", ["pytest", "unittest"]) -def test_unittest_expected_failure_for_failing_test_is_xfail(testdir, runner): - script = testdir.makepyfile( +def test_unittest_expected_failure_for_failing_test_is_xfail( + pytester: Pytester, runner +) -> None: + script = pytester.makepyfile( """ import unittest class MyTestCase(unittest.TestCase): @@ -746,19 +753,21 @@ def test_failing_test_is_xfail(self): """ ) if runner == "pytest": - result = testdir.runpytest("-rxX") + result = pytester.runpytest("-rxX") result.stdout.fnmatch_lines( ["*XFAIL*MyTestCase*test_failing_test_is_xfail*", "*1 xfailed*"] ) else: - result = testdir.runpython(script) + result = pytester.runpython(script) result.stderr.fnmatch_lines(["*1 test in*", "*OK*(expected failures=1)*"]) assert result.ret == 0 @pytest.mark.parametrize("runner", ["pytest", "unittest"]) -def test_unittest_expected_failure_for_passing_test_is_fail(testdir, runner): - script = testdir.makepyfile( +def test_unittest_expected_failure_for_passing_test_is_fail( + pytester: Pytester, runner +) -> None: + script = pytester.makepyfile( """ import unittest class MyTestCase(unittest.TestCase): @@ -771,20 +780,20 @@ def test_passing_test_is_fail(self): ) if runner == "pytest": - result = testdir.runpytest("-rxX") + result = pytester.runpytest("-rxX") result.stdout.fnmatch_lines( ["*MyTestCase*test_passing_test_is_fail*", "*1 failed*"] ) else: - result = testdir.runpython(script) + result = pytester.runpython(script) result.stderr.fnmatch_lines(["*1 test in*", "*(unexpected successes=1)*"]) assert result.ret == 1 @pytest.mark.parametrize("stmt", ["return", "yield"]) -def test_unittest_setup_interaction(testdir: Testdir, stmt: str) -> None: - testdir.makepyfile( +def test_unittest_setup_interaction(pytester: Pytester, stmt: str) -> None: + pytester.makepyfile( """ import unittest import pytest @@ -811,12 +820,12 @@ def test_classattr(self): stmt=stmt ) ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*3 passed*"]) -def test_non_unittest_no_setupclass_support(testdir): - testpath = testdir.makepyfile( +def test_non_unittest_no_setupclass_support(pytester: Pytester) -> None: + testpath = pytester.makepyfile( """ class TestFoo(object): x = 0 @@ -837,12 +846,12 @@ def test_not_teareddown(): """ ) - reprec = testdir.inline_run(testpath) + reprec = pytester.inline_run(testpath) reprec.assertoutcome(passed=2) -def test_no_teardown_if_setupclass_failed(testdir): - testpath = testdir.makepyfile( +def test_no_teardown_if_setupclass_failed(pytester: Pytester) -> None: + testpath = pytester.makepyfile( """ import unittest @@ -865,13 +874,13 @@ def test_notTornDown(): assert MyTestCase.x == 1 """ ) - reprec = testdir.inline_run(testpath) + reprec = pytester.inline_run(testpath) reprec.assertoutcome(passed=1, failed=1) -def test_cleanup_functions(testdir): +def test_cleanup_functions(pytester: Pytester) -> None: """Ensure functions added with addCleanup are always called after each test ends (#6947)""" - testdir.makepyfile( + pytester.makepyfile( """ import unittest @@ -890,7 +899,7 @@ def test_func_3_check_cleanups(self): assert cleanups == ["test_func_1", "test_func_2"] """ ) - result = testdir.runpytest("-v") + result = pytester.runpytest("-v") result.stdout.fnmatch_lines( [ "*::test_func_1 PASSED *", @@ -900,8 +909,8 @@ def test_func_3_check_cleanups(self): ) -def test_issue333_result_clearing(testdir): - testdir.makeconftest( +def test_issue333_result_clearing(pytester: Pytester) -> None: + pytester.makeconftest( """ import pytest @pytest.hookimpl(hookwrapper=True) @@ -910,7 +919,7 @@ def pytest_runtest_call(item): assert 0 """ ) - testdir.makepyfile( + pytester.makepyfile( """ import unittest class TestIt(unittest.TestCase): @@ -919,12 +928,12 @@ def test_func(self): """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(failed=1) -def test_unittest_raise_skip_issue748(testdir): - testdir.makepyfile( +def test_unittest_raise_skip_issue748(pytester: Pytester) -> None: + pytester.makepyfile( test_foo=""" import unittest @@ -933,7 +942,7 @@ def test_one(self): raise unittest.SkipTest('skipping due to reasons') """ ) - result = testdir.runpytest("-v", "-rs") + result = pytester.runpytest("-v", "-rs") result.stdout.fnmatch_lines( """ *SKIP*[1]*test_foo.py*skipping due to reasons* @@ -942,8 +951,8 @@ def test_one(self): ) -def test_unittest_skip_issue1169(testdir): - testdir.makepyfile( +def test_unittest_skip_issue1169(pytester: Pytester) -> None: + pytester.makepyfile( test_foo=""" import unittest @@ -953,7 +962,7 @@ def test_skip(self): self.fail() """ ) - result = testdir.runpytest("-v", "-rs") + result = pytester.runpytest("-v", "-rs") result.stdout.fnmatch_lines( """ *SKIP*[1]*skipping due to reasons* @@ -962,8 +971,8 @@ def test_skip(self): ) -def test_class_method_containing_test_issue1558(testdir): - testdir.makepyfile( +def test_class_method_containing_test_issue1558(pytester: Pytester) -> None: + pytester.makepyfile( test_foo=""" import unittest @@ -975,16 +984,16 @@ def test_should_not_run(self): test_should_not_run.__test__ = False """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(passed=1) @pytest.mark.parametrize("base", ["builtins.object", "unittest.TestCase"]) -def test_usefixtures_marker_on_unittest(base, testdir): +def test_usefixtures_marker_on_unittest(base, pytester: Pytester) -> None: """#3498""" module = base.rsplit(".", 1)[0] pytest.importorskip(module) - testdir.makepyfile( + pytester.makepyfile( conftest=""" import pytest @@ -1013,7 +1022,7 @@ def pytest_collection_modifyitems(items): """ ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest import {module} @@ -1038,16 +1047,16 @@ def test_two(self): ) ) - result = testdir.runpytest("-s") + result = pytester.runpytest("-s") result.assert_outcomes(passed=2) -def test_testcase_handles_init_exceptions(testdir): +def test_testcase_handles_init_exceptions(pytester: Pytester) -> None: """ Regression test to make sure exceptions in the __init__ method are bubbled up correctly. See https://github.com/pytest-dev/pytest/issues/3788 """ - testdir.makepyfile( + pytester.makepyfile( """ from unittest import TestCase import pytest @@ -1058,14 +1067,14 @@ def test_hello(self): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() assert "should raise this exception" in result.stdout.str() result.stdout.no_fnmatch_line("*ERROR at teardown of MyTestCase.test_hello*") -def test_error_message_with_parametrized_fixtures(testdir): - testdir.copy_example("unittest/test_parametrized_fixture_error_message.py") - result = testdir.runpytest() +def test_error_message_with_parametrized_fixtures(pytester: Pytester) -> None: + pytester.copy_example("unittest/test_parametrized_fixture_error_message.py") + result = pytester.runpytest() result.stdout.fnmatch_lines( [ "*test_two does not support fixtures*", @@ -1083,15 +1092,17 @@ def test_error_message_with_parametrized_fixtures(testdir): ("test_setup_skip_module.py", "1 error"), ], ) -def test_setup_inheritance_skipping(testdir, test_name, expected_outcome): +def test_setup_inheritance_skipping( + pytester: Pytester, test_name, expected_outcome +) -> None: """Issue #4700""" - testdir.copy_example(f"unittest/{test_name}") - result = testdir.runpytest() + pytester.copy_example(f"unittest/{test_name}") + result = pytester.runpytest() result.stdout.fnmatch_lines([f"* {expected_outcome} in *"]) -def test_BdbQuit(testdir): - testdir.makepyfile( +def test_BdbQuit(pytester: Pytester) -> None: + pytester.makepyfile( test_foo=""" import unittest @@ -1104,12 +1115,12 @@ def test_should_not_run(self): pass """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reprec.assertoutcome(failed=1, passed=1) -def test_exit_outcome(testdir): - testdir.makepyfile( +def test_exit_outcome(pytester: Pytester) -> None: + pytester.makepyfile( test_foo=""" import pytest import unittest @@ -1122,11 +1133,11 @@ def test_should_not_run(self): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*Exit: pytest_exit called*", "*= no tests ran in *"]) -def test_trace(testdir, monkeypatch): +def test_trace(pytester: Pytester, monkeypatch: MonkeyPatch) -> None: calls = [] def check_call(*args, **kwargs): @@ -1141,7 +1152,7 @@ def runcall(*args, **kwargs): monkeypatch.setattr("_pytest.debugging.pytestPDB._init_pdb", check_call) - p1 = testdir.makepyfile( + p1 = pytester.makepyfile( """ import unittest @@ -1150,12 +1161,12 @@ def test(self): self.assertEqual('foo', 'foo') """ ) - result = testdir.runpytest("--trace", str(p1)) + result = pytester.runpytest("--trace", str(p1)) assert len(calls) == 2 assert result.ret == 0 -def test_pdb_teardown_called(testdir, monkeypatch) -> None: +def test_pdb_teardown_called(pytester: Pytester, monkeypatch: MonkeyPatch) -> None: """Ensure tearDown() is always called when --pdb is given in the command-line. We delay the normal tearDown() calls when --pdb is given, so this ensures we are calling @@ -1166,7 +1177,7 @@ def test_pdb_teardown_called(testdir, monkeypatch) -> None: pytest, "test_pdb_teardown_called_teardowns", teardowns, raising=False ) - testdir.makepyfile( + pytester.makepyfile( """ import unittest import pytest @@ -1182,7 +1193,7 @@ def test_2(self): pass """ ) - result = testdir.runpytest_inprocess("--pdb") + result = pytester.runpytest_inprocess("--pdb") result.stdout.fnmatch_lines("* 2 passed in *") assert teardowns == [ "test_pdb_teardown_called.MyTestCase.test_1", @@ -1191,12 +1202,14 @@ def test_2(self): @pytest.mark.parametrize("mark", ["@unittest.skip", "@pytest.mark.skip"]) -def test_pdb_teardown_skipped(testdir, monkeypatch, mark: str) -> None: +def test_pdb_teardown_skipped( + pytester: Pytester, monkeypatch: MonkeyPatch, mark: str +) -> None: """With --pdb, setUp and tearDown should not be called for skipped tests.""" tracked: List[str] = [] monkeypatch.setattr(pytest, "test_pdb_teardown_skipped", tracked, raising=False) - testdir.makepyfile( + pytester.makepyfile( """ import unittest import pytest @@ -1217,29 +1230,29 @@ def test_1(self): mark=mark ) ) - result = testdir.runpytest_inprocess("--pdb") + result = pytester.runpytest_inprocess("--pdb") result.stdout.fnmatch_lines("* 1 skipped in *") assert tracked == [] -def test_async_support(testdir): +def test_async_support(pytester: Pytester) -> None: pytest.importorskip("unittest.async_case") - testdir.copy_example("unittest/test_unittest_asyncio.py") - reprec = testdir.inline_run() + pytester.copy_example("unittest/test_unittest_asyncio.py") + reprec = pytester.inline_run() reprec.assertoutcome(failed=1, passed=2) -def test_asynctest_support(testdir): +def test_asynctest_support(pytester: Pytester) -> None: """Check asynctest support (#7110)""" pytest.importorskip("asynctest") - testdir.copy_example("unittest/test_unittest_asynctest.py") - reprec = testdir.inline_run() + pytester.copy_example("unittest/test_unittest_asynctest.py") + reprec = pytester.inline_run() reprec.assertoutcome(failed=1, passed=2) -def test_plain_unittest_does_not_support_async(testdir): +def test_plain_unittest_does_not_support_async(pytester: Pytester) -> None: """Async functions in plain unittest.TestCase subclasses are not supported without plugins. This test exists here to avoid introducing this support by accident, leading users @@ -1247,8 +1260,8 @@ def test_plain_unittest_does_not_support_async(testdir): See https://github.com/pytest-dev/pytest-asyncio/issues/180 for more context. """ - testdir.copy_example("unittest/test_unittest_plain_async.py") - result = testdir.runpytest_subprocess() + pytester.copy_example("unittest/test_unittest_plain_async.py") + result = pytester.runpytest_subprocess() if hasattr(sys, "pypy_version_info"): # in PyPy we can't reliable get the warning about the coroutine not being awaited, # because it depends on the coroutine being garbage collected; given that @@ -1265,8 +1278,8 @@ def test_plain_unittest_does_not_support_async(testdir): @pytest.mark.skipif( sys.version_info < (3, 8), reason="Feature introduced in Python 3.8" ) -def test_do_class_cleanups_on_success(testdir): - testpath = testdir.makepyfile( +def test_do_class_cleanups_on_success(pytester: Pytester) -> None: + testpath = pytester.makepyfile( """ import unittest class MyTestCase(unittest.TestCase): @@ -1284,7 +1297,7 @@ def test_cleanup_called_exactly_once(): assert MyTestCase.values == [1] """ ) - reprec = testdir.inline_run(testpath) + reprec = pytester.inline_run(testpath) passed, skipped, failed = reprec.countoutcomes() assert failed == 0 assert passed == 3 @@ -1293,8 +1306,8 @@ def test_cleanup_called_exactly_once(): @pytest.mark.skipif( sys.version_info < (3, 8), reason="Feature introduced in Python 3.8" ) -def test_do_class_cleanups_on_setupclass_failure(testdir): - testpath = testdir.makepyfile( +def test_do_class_cleanups_on_setupclass_failure(pytester: Pytester) -> None: + testpath = pytester.makepyfile( """ import unittest class MyTestCase(unittest.TestCase): @@ -1311,7 +1324,7 @@ def test_cleanup_called_exactly_once(): assert MyTestCase.values == [1] """ ) - reprec = testdir.inline_run(testpath) + reprec = pytester.inline_run(testpath) passed, skipped, failed = reprec.countoutcomes() assert failed == 1 assert passed == 1 @@ -1320,8 +1333,8 @@ def test_cleanup_called_exactly_once(): @pytest.mark.skipif( sys.version_info < (3, 8), reason="Feature introduced in Python 3.8" ) -def test_do_class_cleanups_on_teardownclass_failure(testdir): - testpath = testdir.makepyfile( +def test_do_class_cleanups_on_teardownclass_failure(pytester: Pytester) -> None: + testpath = pytester.makepyfile( """ import unittest class MyTestCase(unittest.TestCase): @@ -1342,13 +1355,13 @@ def test_cleanup_called_exactly_once(): assert MyTestCase.values == [1] """ ) - reprec = testdir.inline_run(testpath) + reprec = pytester.inline_run(testpath) passed, skipped, failed = reprec.countoutcomes() assert passed == 3 -def test_do_cleanups_on_success(testdir): - testpath = testdir.makepyfile( +def test_do_cleanups_on_success(pytester: Pytester) -> None: + testpath = pytester.makepyfile( """ import unittest class MyTestCase(unittest.TestCase): @@ -1365,14 +1378,14 @@ def test_cleanup_called_the_right_number_of_times(): assert MyTestCase.values == [1, 1] """ ) - reprec = testdir.inline_run(testpath) + reprec = pytester.inline_run(testpath) passed, skipped, failed = reprec.countoutcomes() assert failed == 0 assert passed == 3 -def test_do_cleanups_on_setup_failure(testdir): - testpath = testdir.makepyfile( +def test_do_cleanups_on_setup_failure(pytester: Pytester) -> None: + testpath = pytester.makepyfile( """ import unittest class MyTestCase(unittest.TestCase): @@ -1390,14 +1403,14 @@ def test_cleanup_called_the_right_number_of_times(): assert MyTestCase.values == [1, 1] """ ) - reprec = testdir.inline_run(testpath) + reprec = pytester.inline_run(testpath) passed, skipped, failed = reprec.countoutcomes() assert failed == 2 assert passed == 1 -def test_do_cleanups_on_teardown_failure(testdir): - testpath = testdir.makepyfile( +def test_do_cleanups_on_teardown_failure(pytester: Pytester) -> None: + testpath = pytester.makepyfile( """ import unittest class MyTestCase(unittest.TestCase): @@ -1416,7 +1429,7 @@ def test_cleanup_called_the_right_number_of_times(): assert MyTestCase.values == [1, 1] """ ) - reprec = testdir.inline_run(testpath) + reprec = pytester.inline_run(testpath) passed, skipped, failed = reprec.countoutcomes() assert failed == 2 assert passed == 1 From 196b173c8a86833b96f90128a6cc9928e17b6c23 Mon Sep 17 00:00:00 2001 From: antonblr Date: Fri, 18 Dec 2020 12:36:20 -0800 Subject: [PATCH 0025/2772] address comments --- .github/workflows/main.yml | 3 +- src/_pytest/nodes.py | 2 +- src/_pytest/python.py | 2 +- testing/test_debugging.py | 5 +- testing/test_junitxml.py | 175 +++++++++++++++++++++---------------- testing/test_pytester.py | 27 +++--- 6 files changed, 121 insertions(+), 93 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1b6e85fd87e..beb50178528 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -123,8 +123,7 @@ jobs: with: fetch-depth: 0 - name: Set up Python ${{ matrix.python }} - # https://github.com/actions/setup-python/issues/171 - uses: actions/setup-python@v2.1.4 + uses: actions/setup-python@v2 with: python-version: ${{ matrix.python }} - name: Install dependencies diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 27c76a04302..1b3ec5571b1 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -528,7 +528,7 @@ def gethookproxy(self, fspath: "os.PathLike[str]"): warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2) return self.session.gethookproxy(fspath) - def isinitpath(self, path: "os.PathLike[str]") -> bool: + def isinitpath(self, path: Union[str, "os.PathLike[str]"]) -> bool: warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2) return self.session.isinitpath(path) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 018e368f45e..3ff04455fbf 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -660,7 +660,7 @@ def gethookproxy(self, fspath: "os.PathLike[str]"): warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2) return self.session.gethookproxy(fspath) - def isinitpath(self, path: "os.PathLike[str]") -> bool: + def isinitpath(self, path: Union[str, "os.PathLike[str]"]) -> bool: warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2) return self.session.isinitpath(path) diff --git a/testing/test_debugging.py b/testing/test_debugging.py index 8218b7a0ede..e1b57299d25 100644 --- a/testing/test_debugging.py +++ b/testing/test_debugging.py @@ -21,10 +21,11 @@ @pytest.fixture(autouse=True) -def pdb_env(request, monkeypatch: MonkeyPatch): +def pdb_env(request): if "pytester" in request.fixturenames: # Disable pdb++ with inner tests. - monkeypatch.setenv("PDBPP_HIJACK_PDB", "0") + pytester = request.getfixturevalue("pytester") + pytester._monkeypatch.setenv("PDBPP_HIJACK_PDB", "0") def runpdb_and_get_report(pytester: Pytester, source: str): diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index 3e445dcefc5..1c76351eafc 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -7,7 +7,6 @@ from typing import Optional from typing import Tuple from typing import TYPE_CHECKING -from typing import TypeVar from typing import Union from xml.dom import minidom @@ -24,8 +23,6 @@ from _pytest.reports import TestReport from _pytest.store import Store -T = TypeVar("T") - @pytest.fixture(scope="session") def schema() -> xmlschema.XMLSchema: @@ -35,29 +32,34 @@ def schema() -> xmlschema.XMLSchema: return xmlschema.XMLSchema(f) -@pytest.fixture -def run_and_parse(pytester: Pytester, schema: xmlschema.XMLSchema) -> T: - """Fixture that returns a function that can be used to execute pytest and - return the parsed ``DomNode`` of the root xml node. - - The ``family`` parameter is used to configure the ``junit_family`` of the written report. - "xunit2" is also automatically validated against the schema. - """ +class RunAndParse: + def __init__(self, pytester: Pytester, schema: xmlschema.XMLSchema) -> None: + self.pytester = pytester + self.schema = schema - def run( - *args: Union[str, "os.PathLike[str]"], family: Optional[str] = "xunit1", + def __call__( + self, *args: Union[str, "os.PathLike[str]"], family: Optional[str] = "xunit1" ) -> Tuple[RunResult, "DomNode"]: if family: args = ("-o", "junit_family=" + family) + args - xml_path = pytester.path.joinpath("junit.xml") - result = pytester.runpytest("--junitxml=%s" % xml_path, *args) + xml_path = self.pytester.path.joinpath("junit.xml") + result = self.pytester.runpytest("--junitxml=%s" % xml_path, *args) if family == "xunit2": with xml_path.open() as f: - schema.validate(f) + self.schema.validate(f) xmldoc = minidom.parse(str(xml_path)) return result, DomNode(xmldoc) - return cast(T, run) + +@pytest.fixture +def run_and_parse(pytester: Pytester, schema: xmlschema.XMLSchema) -> RunAndParse: + """Fixture that returns a function that can be used to execute pytest and + return the parsed ``DomNode`` of the root xml node. + + The ``family`` parameter is used to configure the ``junit_family`` of the written report. + "xunit2" is also automatically validated against the schema. + """ + return RunAndParse(pytester, schema) def assert_attr(node, **kwargs): @@ -140,7 +142,7 @@ def next_sibling(self): class TestPython: @parametrize_families def test_summing_simple( - self, pytester: Pytester, run_and_parse, xunit_family + self, pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str ) -> None: pytester.makepyfile( """ @@ -166,7 +168,7 @@ def test_xpass(): @parametrize_families def test_summing_simple_with_errors( - self, pytester: Pytester, run_and_parse, xunit_family + self, pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str ) -> None: pytester.makepyfile( """ @@ -195,7 +197,7 @@ def test_xpass(): @parametrize_families def test_hostname_in_xml( - self, pytester: Pytester, run_and_parse, xunit_family + self, pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str ) -> None: pytester.makepyfile( """ @@ -209,7 +211,7 @@ def test_pass(): @parametrize_families def test_timestamp_in_xml( - self, pytester: Pytester, run_and_parse, xunit_family + self, pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str ) -> None: pytester.makepyfile( """ @@ -224,7 +226,7 @@ def test_pass(): assert start_time <= timestamp < datetime.now() def test_timing_function( - self, pytester: Pytester, run_and_parse, mock_timing + self, pytester: Pytester, run_and_parse: RunAndParse, mock_timing ) -> None: pytester.makepyfile( """ @@ -248,8 +250,8 @@ def test_junit_duration_report( self, pytester: Pytester, monkeypatch: MonkeyPatch, - duration_report, - run_and_parse, + duration_report: str, + run_and_parse: RunAndParse, ) -> None: # mock LogXML.node_reporter so it always sets a known duration to each test report object @@ -279,7 +281,9 @@ def test_foo(): assert val == 1.0 @parametrize_families - def test_setup_error(self, pytester: Pytester, run_and_parse, xunit_family) -> None: + def test_setup_error( + self, pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str + ) -> None: pytester.makepyfile( """ import pytest @@ -303,7 +307,7 @@ def test_function(arg): @parametrize_families def test_teardown_error( - self, pytester: Pytester, run_and_parse, xunit_family + self, pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str ) -> None: pytester.makepyfile( """ @@ -328,7 +332,7 @@ def test_function(arg): @parametrize_families def test_call_failure_teardown_error( - self, pytester: Pytester, run_and_parse, xunit_family + self, pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str ) -> None: pytester.makepyfile( """ @@ -359,7 +363,7 @@ def test_function(arg): @parametrize_families def test_skip_contains_name_reason( - self, pytester: Pytester, run_and_parse, xunit_family + self, pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str ) -> None: pytester.makepyfile( """ @@ -379,7 +383,7 @@ def test_skip(): @parametrize_families def test_mark_skip_contains_name_reason( - self, pytester: Pytester, run_and_parse, xunit_family + self, pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str ) -> None: pytester.makepyfile( """ @@ -402,7 +406,7 @@ def test_skip(): @parametrize_families def test_mark_skipif_contains_name_reason( - self, pytester: Pytester, run_and_parse, xunit_family + self, pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str ) -> None: pytester.makepyfile( """ @@ -426,7 +430,7 @@ def test_skip(): @parametrize_families def test_mark_skip_doesnt_capture_output( - self, pytester: Pytester, run_and_parse, xunit_family + self, pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str ) -> None: pytester.makepyfile( """ @@ -443,7 +447,7 @@ def test_skip(): @parametrize_families def test_classname_instance( - self, pytester: Pytester, run_and_parse, xunit_family + self, pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str ) -> None: pytester.makepyfile( """ @@ -463,7 +467,7 @@ def test_method(self): @parametrize_families def test_classname_nested_dir( - self, pytester: Pytester, run_and_parse, xunit_family + self, pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str ) -> None: p = pytester.mkdir("sub").joinpath("test_hello.py") p.write_text("def test_func(): 0/0") @@ -476,7 +480,7 @@ def test_classname_nested_dir( @parametrize_families def test_internal_error( - self, pytester: Pytester, run_and_parse, xunit_family + self, pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str ) -> None: pytester.makeconftest("def pytest_runtest_protocol(): 0 / 0") pytester.makepyfile("def test_function(): pass") @@ -495,7 +499,11 @@ def test_internal_error( ) @parametrize_families def test_failure_function( - self, pytester: Pytester, junit_logging, run_and_parse, xunit_family + self, + pytester: Pytester, + junit_logging, + run_and_parse: RunAndParse, + xunit_family, ) -> None: pytester.makepyfile( """ @@ -559,7 +567,7 @@ def test_fail(): @parametrize_families def test_failure_verbose_message( - self, pytester: Pytester, run_and_parse, xunit_family + self, pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str ) -> None: pytester.makepyfile( """ @@ -576,7 +584,7 @@ def test_fail(): @parametrize_families def test_failure_escape( - self, pytester: Pytester, run_and_parse, xunit_family + self, pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str ) -> None: pytester.makepyfile( """ @@ -606,7 +614,7 @@ def test_func(arg1): @parametrize_families def test_junit_prefixing( - self, pytester: Pytester, run_and_parse, xunit_family + self, pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str ) -> None: pytester.makepyfile( """ @@ -630,7 +638,7 @@ def test_hello(self): @parametrize_families def test_xfailure_function( - self, pytester: Pytester, run_and_parse, xunit_family + self, pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str ) -> None: pytester.makepyfile( """ @@ -650,7 +658,7 @@ def test_xfail(): @parametrize_families def test_xfailure_marker( - self, pytester: Pytester, run_and_parse, xunit_family + self, pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str ) -> None: pytester.makepyfile( """ @@ -673,7 +681,7 @@ def test_xfail(): "junit_logging", ["no", "log", "system-out", "system-err", "out-err", "all"] ) def test_xfail_captures_output_once( - self, pytester: Pytester, junit_logging, run_and_parse + self, pytester: Pytester, junit_logging: str, run_and_parse: RunAndParse ) -> None: pytester.makepyfile( """ @@ -702,7 +710,7 @@ def test_fail(): @parametrize_families def test_xfailure_xpass( - self, pytester: Pytester, run_and_parse, xunit_family + self, pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str ) -> None: pytester.makepyfile( """ @@ -721,7 +729,7 @@ def test_xpass(): @parametrize_families def test_xfailure_xpass_strict( - self, pytester: Pytester, run_and_parse, xunit_family + self, pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str ) -> None: pytester.makepyfile( """ @@ -742,7 +750,7 @@ def test_xpass(): @parametrize_families def test_collect_error( - self, pytester: Pytester, run_and_parse, xunit_family + self, pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str ) -> None: pytester.makepyfile("syntax error") result, dom = run_and_parse(family=xunit_family) @@ -754,7 +762,7 @@ def test_collect_error( fnode.assert_attr(message="collection failure") assert "SyntaxError" in fnode.toxml() - def test_unicode(self, pytester: Pytester, run_and_parse) -> None: + def test_unicode(self, pytester: Pytester, run_and_parse: RunAndParse) -> None: value = "hx\xc4\x85\xc4\x87\n" pytester.makepyfile( """\ @@ -771,7 +779,9 @@ def test_hello(): fnode = tnode.find_first_by_tag("failure") assert "hx" in fnode.toxml() - def test_assertion_binchars(self, pytester: Pytester, run_and_parse) -> None: + def test_assertion_binchars( + self, pytester: Pytester, run_and_parse: RunAndParse + ) -> None: """This test did fail when the escaping wasn't strict.""" pytester.makepyfile( """ @@ -788,7 +798,7 @@ def test_str_compare(): @pytest.mark.parametrize("junit_logging", ["no", "system-out"]) def test_pass_captures_stdout( - self, pytester: Pytester, run_and_parse, junit_logging + self, pytester: Pytester, run_and_parse: RunAndParse, junit_logging: str ) -> None: pytester.makepyfile( """ @@ -811,7 +821,7 @@ def test_pass(): @pytest.mark.parametrize("junit_logging", ["no", "system-err"]) def test_pass_captures_stderr( - self, pytester: Pytester, run_and_parse, junit_logging + self, pytester: Pytester, run_and_parse: RunAndParse, junit_logging: str ) -> None: pytester.makepyfile( """ @@ -835,7 +845,7 @@ def test_pass(): @pytest.mark.parametrize("junit_logging", ["no", "system-out"]) def test_setup_error_captures_stdout( - self, pytester: Pytester, run_and_parse, junit_logging + self, pytester: Pytester, run_and_parse: RunAndParse, junit_logging: str ) -> None: pytester.makepyfile( """ @@ -864,7 +874,7 @@ def test_function(arg): @pytest.mark.parametrize("junit_logging", ["no", "system-err"]) def test_setup_error_captures_stderr( - self, pytester: Pytester, run_and_parse, junit_logging + self, pytester: Pytester, run_and_parse: RunAndParse, junit_logging: str ) -> None: pytester.makepyfile( """ @@ -894,7 +904,7 @@ def test_function(arg): @pytest.mark.parametrize("junit_logging", ["no", "system-out"]) def test_avoid_double_stdout( - self, pytester: Pytester, run_and_parse, junit_logging + self, pytester: Pytester, run_and_parse: RunAndParse, junit_logging: str ) -> None: pytester.makepyfile( """ @@ -964,7 +974,7 @@ def getini(self, name): class TestNonPython: @parametrize_families def test_summing_simple( - self, pytester: Pytester, run_and_parse, xunit_family + self, pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str ) -> None: pytester.makeconftest( """ @@ -992,7 +1002,7 @@ def repr_failure(self, excinfo): @pytest.mark.parametrize("junit_logging", ["no", "system-out"]) -def test_nullbyte(pytester: Pytester, junit_logging) -> None: +def test_nullbyte(pytester: Pytester, junit_logging: str) -> None: # A null byte can not occur in XML (see section 2.2 of the spec) pytester.makepyfile( """ @@ -1014,7 +1024,7 @@ def test_print_nullbyte(): @pytest.mark.parametrize("junit_logging", ["no", "system-out"]) -def test_nullbyte_replace(pytester: Pytester, junit_logging) -> None: +def test_nullbyte_replace(pytester: Pytester, junit_logging: str) -> None: # Check if the null byte gets replaced pytester.makepyfile( """ @@ -1114,7 +1124,9 @@ def test_logxml_check_isdir(pytester: Pytester) -> None: result.stderr.fnmatch_lines(["*--junitxml must be a filename*"]) -def test_escaped_parametrized_names_xml(pytester: Pytester, run_and_parse) -> None: +def test_escaped_parametrized_names_xml( + pytester: Pytester, run_and_parse: RunAndParse +) -> None: pytester.makepyfile( """\ import pytest @@ -1130,7 +1142,7 @@ def test_func(char): def test_double_colon_split_function_issue469( - pytester: Pytester, run_and_parse + pytester: Pytester, run_and_parse: RunAndParse ) -> None: pytester.makepyfile( """ @@ -1147,7 +1159,9 @@ def test_func(param): node.assert_attr(name="test_func[double::colon]") -def test_double_colon_split_method_issue469(pytester: Pytester, run_and_parse) -> None: +def test_double_colon_split_method_issue469( + pytester: Pytester, run_and_parse: RunAndParse +) -> None: pytester.makepyfile( """ import pytest @@ -1194,7 +1208,7 @@ class Report(BaseReport): log.pytest_sessionfinish() -def test_record_property(pytester: Pytester, run_and_parse) -> None: +def test_record_property(pytester: Pytester, run_and_parse: RunAndParse) -> None: pytester.makepyfile( """ import pytest @@ -1216,7 +1230,9 @@ def test_record(record_property, other): result.stdout.fnmatch_lines(["*= 1 passed in *"]) -def test_record_property_same_name(pytester: Pytester, run_and_parse) -> None: +def test_record_property_same_name( + pytester: Pytester, run_and_parse: RunAndParse +) -> None: pytester.makepyfile( """ def test_record_with_same_name(record_property): @@ -1234,7 +1250,9 @@ def test_record_with_same_name(record_property): @pytest.mark.parametrize("fixture_name", ["record_property", "record_xml_attribute"]) -def test_record_fixtures_without_junitxml(pytester: Pytester, fixture_name) -> None: +def test_record_fixtures_without_junitxml( + pytester: Pytester, fixture_name: str +) -> None: pytester.makepyfile( """ def test_record({fixture_name}): @@ -1248,7 +1266,7 @@ def test_record({fixture_name}): @pytest.mark.filterwarnings("default") -def test_record_attribute(pytester: Pytester, run_and_parse) -> None: +def test_record_attribute(pytester: Pytester, run_and_parse: RunAndParse) -> None: pytester.makeini( """ [pytest] @@ -1279,7 +1297,7 @@ def test_record(record_xml_attribute, other): @pytest.mark.filterwarnings("default") @pytest.mark.parametrize("fixture_name", ["record_xml_attribute", "record_property"]) def test_record_fixtures_xunit2( - pytester: Pytester, fixture_name, run_and_parse + pytester: Pytester, fixture_name: str, run_and_parse: RunAndParse ) -> None: """Ensure record_xml_attribute and record_property drop values when outside of legacy family.""" pytester.makeini( @@ -1318,7 +1336,7 @@ def test_record({fixture_name}, other): def test_random_report_log_xdist( - pytester: Pytester, monkeypatch: MonkeyPatch, run_and_parse + pytester: Pytester, monkeypatch: MonkeyPatch, run_and_parse: RunAndParse ) -> None: """`xdist` calls pytest_runtest_logreport as they are executed by the workers, with nodes from several nodes overlapping, so junitxml must cope with that @@ -1344,7 +1362,9 @@ def test_x(i): @parametrize_families -def test_root_testsuites_tag(pytester: Pytester, run_and_parse, xunit_family) -> None: +def test_root_testsuites_tag( + pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str +) -> None: pytester.makepyfile( """ def test_x(): @@ -1358,7 +1378,7 @@ def test_x(): assert suite_node.tag == "testsuite" -def test_runs_twice(pytester: Pytester, run_and_parse) -> None: +def test_runs_twice(pytester: Pytester, run_and_parse: RunAndParse) -> None: f = pytester.makepyfile( """ def test_pass(): @@ -1373,7 +1393,7 @@ def test_pass(): def test_runs_twice_xdist( - pytester: Pytester, monkeypatch: MonkeyPatch, run_and_parse + pytester: Pytester, monkeypatch: MonkeyPatch, run_and_parse: RunAndParse ) -> None: pytest.importorskip("xdist") monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD") @@ -1390,7 +1410,7 @@ def test_pass(): assert first == second -def test_fancy_items_regression(pytester: Pytester, run_and_parse) -> None: +def test_fancy_items_regression(pytester: Pytester, run_and_parse: RunAndParse) -> None: # issue 1259 pytester.makeconftest( """ @@ -1443,7 +1463,7 @@ def test_pass(): @parametrize_families -def test_global_properties(pytester: Pytester, xunit_family) -> None: +def test_global_properties(pytester: Pytester, xunit_family: str) -> None: path = pytester.path.joinpath("test_global_properties.xml") log = LogXML(str(path), None, family=xunit_family) @@ -1505,7 +1525,7 @@ class Report(BaseReport): @parametrize_families def test_record_testsuite_property( - pytester: Pytester, run_and_parse, xunit_family + pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str ) -> None: pytester.makepyfile( """ @@ -1538,7 +1558,9 @@ def test_func1(record_testsuite_property): @pytest.mark.parametrize("junit", [True, False]) -def test_record_testsuite_property_type_checking(pytester: Pytester, junit) -> None: +def test_record_testsuite_property_type_checking( + pytester: Pytester, junit: bool +) -> None: pytester.makepyfile( """ def test_func1(record_testsuite_property): @@ -1556,7 +1578,7 @@ def test_func1(record_testsuite_property): @pytest.mark.parametrize("suite_name", ["my_suite", ""]) @parametrize_families def test_set_suite_name( - pytester: Pytester, suite_name, run_and_parse, xunit_family + pytester: Pytester, suite_name: str, run_and_parse: RunAndParse, xunit_family: str ) -> None: if suite_name: pytester.makeini( @@ -1585,7 +1607,9 @@ def test_func(): node.assert_attr(name=expected) -def test_escaped_skipreason_issue3533(pytester: Pytester, run_and_parse) -> None: +def test_escaped_skipreason_issue3533( + pytester: Pytester, run_and_parse: RunAndParse +) -> None: pytester.makepyfile( """ import pytest @@ -1603,7 +1627,7 @@ def test_skip(): @parametrize_families def test_logging_passing_tests_disabled_does_not_log_test_output( - pytester: Pytester, run_and_parse, xunit_family + pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str ) -> None: pytester.makeini( """ @@ -1637,7 +1661,10 @@ def test_func(): @parametrize_families @pytest.mark.parametrize("junit_logging", ["no", "system-out", "system-err"]) def test_logging_passing_tests_disabled_logs_output_for_failing_test_issue5430( - pytester: Pytester, junit_logging, run_and_parse, xunit_family + pytester: Pytester, + junit_logging: str, + run_and_parse: RunAndParse, + xunit_family: str, ) -> None: pytester.makeini( """ diff --git a/testing/test_pytester.py b/testing/test_pytester.py index a9ba1a046f1..57d6f4fd9eb 100644 --- a/testing/test_pytester.py +++ b/testing/test_pytester.py @@ -92,7 +92,7 @@ def test_hello(pytester): result.assert_outcomes(passed=1) -def test_pytester_with_doctest(pytester: Pytester): +def test_pytester_with_doctest(pytester: Pytester) -> None: """Check that pytester can be used within doctests. It used to use `request.function`, which is `None` with doctests.""" @@ -314,20 +314,19 @@ def test_cwd_snapshot(pytester: Pytester) -> None: class TestSysModulesSnapshot: key = "my-test-module" - mod = ModuleType("something") def test_remove_added(self) -> None: original = dict(sys.modules) assert self.key not in sys.modules snapshot = SysModulesSnapshot() - sys.modules[self.key] = "something" # type: ignore + sys.modules[self.key] = ModuleType("something") assert self.key in sys.modules snapshot.restore() assert sys.modules == original def test_add_removed(self, monkeypatch: MonkeyPatch) -> None: assert self.key not in sys.modules - monkeypatch.setitem(sys.modules, self.key, self.mod) + monkeypatch.setitem(sys.modules, self.key, ModuleType("something")) assert self.key in sys.modules original = dict(sys.modules) snapshot = SysModulesSnapshot() @@ -338,11 +337,11 @@ def test_add_removed(self, monkeypatch: MonkeyPatch) -> None: def test_restore_reloaded(self, monkeypatch: MonkeyPatch) -> None: assert self.key not in sys.modules - monkeypatch.setitem(sys.modules, self.key, self.mod) + monkeypatch.setitem(sys.modules, self.key, ModuleType("something")) assert self.key in sys.modules original = dict(sys.modules) snapshot = SysModulesSnapshot() - sys.modules[self.key] = "something else" # type: ignore + sys.modules[self.key] = ModuleType("something else") snapshot.restore() assert sys.modules == original @@ -358,9 +357,9 @@ def preserve(name): return name in (key[0], key[1], "some-other-key") snapshot = SysModulesSnapshot(preserve=preserve) - sys.modules[key[0]] = original[key[0]] = "something else0" # type: ignore - sys.modules[key[1]] = original[key[1]] = "something else1" # type: ignore - sys.modules[key[2]] = "something else2" # type: ignore + sys.modules[key[0]] = original[key[0]] = ModuleType("something else0") + sys.modules[key[1]] = original[key[1]] = ModuleType("something else1") + sys.modules[key[2]] = ModuleType("something else2") snapshot.restore() assert sys.modules == original @@ -368,7 +367,7 @@ def test_preserve_container(self, monkeypatch: MonkeyPatch) -> None: original = dict(sys.modules) assert self.key not in original replacement = dict(sys.modules) - replacement[self.key] = "life of brian" # type: ignore + replacement[self.key] = ModuleType("life of brian") snapshot = SysModulesSnapshot() monkeypatch.setattr(sys, "modules", replacement) snapshot.restore() @@ -442,7 +441,9 @@ def test_one(): assert pytester.runpytest(testfile).ret == 0 """ ) - result = pytester.runpytest("-p", "pytester", "--runpytest", "subprocess", testfile) + result = pytester.runpytest_inprocess( + "-p", "pytester", "--runpytest", "subprocess", testfile + ) assert result.ret == 0 @@ -777,7 +778,7 @@ def test_error2(bad_fixture): assert result.parseoutcomes() == {"errors": 2} -def test_parse_summary_line_always_plural(): +def test_parse_summary_line_always_plural() -> None: """Parsing summaries always returns plural nouns (#6505)""" lines = [ "some output 1", @@ -812,6 +813,6 @@ def test_makefile_joins_absolute_path(pytester: Pytester) -> None: assert str(p1) == str(pytester.path / "absfile.py") -def test_testtmproot(testdir): +def test_testtmproot(testdir) -> None: """Check test_tmproot is a py.path attribute for backward compatibility.""" assert testdir.test_tmproot.check(dir=1) From 73586be08fe33c483ae3c3509a05459969ba2ab9 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 18 Dec 2020 20:33:39 +0200 Subject: [PATCH 0026/2772] terminal: remove unused union arm in WarningReport.fslocation --- src/_pytest/terminal.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index f5d4e1f8ddc..d3d1a4b666e 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -285,15 +285,13 @@ class WarningReport: User friendly message about the warning. :ivar str|None nodeid: nodeid that generated the warning (see ``get_location``). - :ivar tuple|py.path.local fslocation: + :ivar tuple fslocation: File system location of the source of the warning (see ``get_location``). """ message = attr.ib(type=str) nodeid = attr.ib(type=Optional[str], default=None) - fslocation = attr.ib( - type=Optional[Union[Tuple[str, int], py.path.local]], default=None - ) + fslocation = attr.ib(type=Optional[Tuple[str, int]], default=None) count_towards_summary = True def get_location(self, config: Config) -> Optional[str]: @@ -301,14 +299,9 @@ def get_location(self, config: Config) -> Optional[str]: if self.nodeid: return self.nodeid if self.fslocation: - if isinstance(self.fslocation, tuple) and len(self.fslocation) >= 2: - filename, linenum = self.fslocation[:2] - relpath = bestrelpath( - config.invocation_params.dir, absolutepath(filename) - ) - return f"{relpath}:{linenum}" - else: - return str(self.fslocation) + filename, linenum = self.fslocation + relpath = bestrelpath(config.invocation_params.dir, absolutepath(filename)) + return f"{relpath}:{linenum}" return None From 2c05a7babb4fa31775730e596a908f7c1f861765 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 18 Dec 2020 20:39:33 +0200 Subject: [PATCH 0027/2772] config: let main() accept any os.PathLike instead of just py.path.local Really it ought to only take the List[str], but for backward compatibility, at least get rid of the explicit py.path.local check. --- src/_pytest/config/__init__.py | 10 ++++++---- testing/test_collection.py | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 0df4ffa01c1..c9a0e78bfcf 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -128,7 +128,7 @@ def filter_traceback_for_conftest_import_failure( def main( - args: Optional[Union[List[str], py.path.local]] = None, + args: Optional[Union[List[str], "os.PathLike[str]"]] = None, plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None, ) -> Union[int, ExitCode]: """Perform an in-process test run. @@ -295,13 +295,15 @@ def get_plugin_manager() -> "PytestPluginManager": def _prepareconfig( - args: Optional[Union[py.path.local, List[str]]] = None, + args: Optional[Union[List[str], "os.PathLike[str]"]] = None, plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None, ) -> "Config": if args is None: args = sys.argv[1:] - elif isinstance(args, py.path.local): - args = [str(args)] + # TODO: Remove type-ignore after next mypy release. + # https://github.com/python/typeshed/commit/076983eec45e739c68551cb6119fd7d85fd4afa9 + elif isinstance(args, os.PathLike): # type: ignore[misc] + args = [os.fspath(args)] elif not isinstance(args, list): msg = "`args` parameter expected to be a list of strings, got: {!r} (type: {})" raise TypeError(msg.format(args, type(args))) diff --git a/testing/test_collection.py b/testing/test_collection.py index 2d03fda39de..9733b4fbd47 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -277,7 +277,7 @@ def pytest_collect_file(self, path): wascalled.append(path) pytester.makefile(".abc", "xyz") - pytest.main(py.path.local(pytester.path), plugins=[Plugin()]) + pytest.main(pytester.path, plugins=[Plugin()]) assert len(wascalled) == 1 assert wascalled[0].ext == ".abc" From 042d12fae6e03f97ac25311504f6697154eff08e Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 19 Dec 2020 14:02:24 +0200 Subject: [PATCH 0028/2772] doctest: use Path instead of py.path where possible --- src/_pytest/doctest.py | 21 +++++++++++---------- testing/test_doctest.py | 17 ++++++++--------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py index d0b6b4c4185..24f8882579b 100644 --- a/src/_pytest/doctest.py +++ b/src/_pytest/doctest.py @@ -36,6 +36,7 @@ from _pytest.fixtures import FixtureRequest from _pytest.nodes import Collector from _pytest.outcomes import OutcomeException +from _pytest.pathlib import fnmatch_ex from _pytest.pathlib import import_path from _pytest.python_api import approx from _pytest.warning_types import PytestWarning @@ -120,32 +121,32 @@ def pytest_unconfigure() -> None: def pytest_collect_file( - path: py.path.local, parent: Collector, + fspath: Path, path: py.path.local, parent: Collector, ) -> Optional[Union["DoctestModule", "DoctestTextfile"]]: config = parent.config - if path.ext == ".py": - if config.option.doctestmodules and not _is_setup_py(path): + if fspath.suffix == ".py": + if config.option.doctestmodules and not _is_setup_py(fspath): mod: DoctestModule = DoctestModule.from_parent(parent, fspath=path) return mod - elif _is_doctest(config, path, parent): + elif _is_doctest(config, fspath, parent): txt: DoctestTextfile = DoctestTextfile.from_parent(parent, fspath=path) return txt return None -def _is_setup_py(path: py.path.local) -> bool: - if path.basename != "setup.py": +def _is_setup_py(path: Path) -> bool: + if path.name != "setup.py": return False - contents = path.read_binary() + contents = path.read_bytes() return b"setuptools" in contents or b"distutils" in contents -def _is_doctest(config: Config, path: py.path.local, parent) -> bool: - if path.ext in (".txt", ".rst") and parent.session.isinitpath(path): +def _is_doctest(config: Config, path: Path, parent: Collector) -> bool: + if path.suffix in (".txt", ".rst") and parent.session.isinitpath(path): return True globs = config.getoption("doctestglob") or ["test*.txt"] for glob in globs: - if path.check(fnmatch=glob): + if fnmatch_ex(glob, path): return True return False diff --git a/testing/test_doctest.py b/testing/test_doctest.py index 6e3880330a9..08d0aacf68c 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -1,10 +1,9 @@ import inspect import textwrap +from pathlib import Path from typing import Callable from typing import Optional -import py - import pytest from _pytest.doctest import _get_checker from _pytest.doctest import _is_mocked @@ -1496,25 +1495,25 @@ def test_warning_on_unwrap_of_broken_object( assert inspect.unwrap.__module__ == "inspect" -def test_is_setup_py_not_named_setup_py(tmp_path): +def test_is_setup_py_not_named_setup_py(tmp_path: Path) -> None: not_setup_py = tmp_path.joinpath("not_setup.py") not_setup_py.write_text('from setuptools import setup; setup(name="foo")') - assert not _is_setup_py(py.path.local(str(not_setup_py))) + assert not _is_setup_py(not_setup_py) @pytest.mark.parametrize("mod", ("setuptools", "distutils.core")) -def test_is_setup_py_is_a_setup_py(tmpdir, mod): - setup_py = tmpdir.join("setup.py") - setup_py.write(f'from {mod} import setup; setup(name="foo")') +def test_is_setup_py_is_a_setup_py(tmp_path: Path, mod: str) -> None: + setup_py = tmp_path.joinpath("setup.py") + setup_py.write_text(f'from {mod} import setup; setup(name="foo")', "utf-8") assert _is_setup_py(setup_py) @pytest.mark.parametrize("mod", ("setuptools", "distutils.core")) -def test_is_setup_py_different_encoding(tmp_path, mod): +def test_is_setup_py_different_encoding(tmp_path: Path, mod: str) -> None: setup_py = tmp_path.joinpath("setup.py") contents = ( "# -*- coding: cp1252 -*-\n" 'from {} import setup; setup(name="foo", description="€")\n'.format(mod) ) setup_py.write_bytes(contents.encode("cp1252")) - assert _is_setup_py(py.path.local(str(setup_py))) + assert _is_setup_py(setup_py) From 7aa224083205adb650a7b1132e6b9e861361426e Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 19 Dec 2020 14:06:17 +0200 Subject: [PATCH 0029/2772] testing/test_nodes: fix fake session to be more accurate The type of _initialpaths is `FrozenSet[Path]`. --- testing/test_nodes.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/testing/test_nodes.py b/testing/test_nodes.py index bae31f0a39c..59d9f409eac 100644 --- a/testing/test_nodes.py +++ b/testing/test_nodes.py @@ -1,3 +1,4 @@ +from pathlib import Path from typing import cast from typing import List from typing import Type @@ -69,23 +70,23 @@ def test(): def test__check_initialpaths_for_relpath() -> None: """Ensure that it handles dirs, and does not always use dirname.""" - cwd = py.path.local() + cwd = Path.cwd() class FakeSession1: - _initialpaths = [cwd] + _initialpaths = frozenset({cwd}) session = cast(pytest.Session, FakeSession1) - assert nodes._check_initialpaths_for_relpath(session, cwd) == "" + assert nodes._check_initialpaths_for_relpath(session, py.path.local(cwd)) == "" - sub = cwd.join("file") + sub = cwd / "file" class FakeSession2: - _initialpaths = [cwd] + _initialpaths = frozenset({cwd}) session = cast(pytest.Session, FakeSession2) - assert nodes._check_initialpaths_for_relpath(session, sub) == "file" + assert nodes._check_initialpaths_for_relpath(session, py.path.local(sub)) == "file" outside = py.path.local("/outside") assert nodes._check_initialpaths_for_relpath(session, outside) is None From 2ec372df8b987207efc4ad0f33c2f82df5c9e2e5 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 19 Dec 2020 22:19:51 +0200 Subject: [PATCH 0030/2772] mark: export pytest.Mark for typing purposes The type cannot be constructed directly, but is exported for use in type annotations, since it is reachable through existing public API. --- changelog/7469.deprecation.rst | 5 +++++ changelog/7469.feature.rst | 10 +++++++++ doc/en/reference.rst | 4 ++-- src/_pytest/mark/structures.py | 39 +++++++++++++++++++++++++--------- src/pytest/__init__.py | 2 ++ 5 files changed, 48 insertions(+), 12 deletions(-) create mode 100644 changelog/7469.deprecation.rst create mode 100644 changelog/7469.feature.rst diff --git a/changelog/7469.deprecation.rst b/changelog/7469.deprecation.rst new file mode 100644 index 00000000000..79419dacef2 --- /dev/null +++ b/changelog/7469.deprecation.rst @@ -0,0 +1,5 @@ +Directly constructing the following classes is now deprecated: + +- ``_pytest.mark.structures.Mark`` + +These have always been considered private, but now issue a deprecation warning, which may become a hard error in pytest 7.0.0. diff --git a/changelog/7469.feature.rst b/changelog/7469.feature.rst new file mode 100644 index 00000000000..f4bc52414bc --- /dev/null +++ b/changelog/7469.feature.rst @@ -0,0 +1,10 @@ +The types of objects used in pytest's API are now exported so they may be used in type annotations. + +The newly-exported types are: + +- ``pytest.Mark`` for :class:`marks `. + +Constructing them directly is not supported; they are only meant for use in type annotations. +Doing so will emit a deprecation warning, and may become a hard-error in pytest 7.0. + +Subclassing them is also not supported. This is not currently enforced at runtime, but is detected by type-checkers such as mypy. diff --git a/doc/en/reference.rst b/doc/en/reference.rst index 8aa95ca6448..3fc62ee7279 100644 --- a/doc/en/reference.rst +++ b/doc/en/reference.rst @@ -239,7 +239,7 @@ For example: def test_function(): ... -Will create and attach a :class:`Mark <_pytest.mark.structures.Mark>` object to the collected +Will create and attach a :class:`Mark ` object to the collected :class:`Item `, which can then be accessed by fixtures or hooks with :meth:`Node.iter_markers <_pytest.nodes.Node.iter_markers>`. The ``mark`` object will have the following attributes: @@ -863,7 +863,7 @@ MarkGenerator Mark ~~~~ -.. autoclass:: _pytest.mark.structures.Mark +.. autoclass:: pytest.Mark() :members: diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index 6c126cf4a29..29b9586871f 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -28,6 +28,7 @@ from ..compat import NOTSET from ..compat import NotSetType from _pytest.config import Config +from _pytest.deprecated import check_ispytest from _pytest.outcomes import fail from _pytest.warning_types import PytestUnknownMarkWarning @@ -200,21 +201,38 @@ def _for_parametrize( @final -@attr.s(frozen=True) +@attr.s(frozen=True, init=False, auto_attribs=True) class Mark: #: Name of the mark. - name = attr.ib(type=str) + name: str #: Positional arguments of the mark decorator. - args = attr.ib(type=Tuple[Any, ...]) + args: Tuple[Any, ...] #: Keyword arguments of the mark decorator. - kwargs = attr.ib(type=Mapping[str, Any]) + kwargs: Mapping[str, Any] #: Source Mark for ids with parametrize Marks. - _param_ids_from = attr.ib(type=Optional["Mark"], default=None, repr=False) + _param_ids_from: Optional["Mark"] = attr.ib(default=None, repr=False) #: Resolved/generated ids with parametrize Marks. - _param_ids_generated = attr.ib( - type=Optional[Sequence[str]], default=None, repr=False - ) + _param_ids_generated: Optional[Sequence[str]] = attr.ib(default=None, repr=False) + + def __init__( + self, + name: str, + args: Tuple[Any, ...], + kwargs: Mapping[str, Any], + param_ids_from: Optional["Mark"] = None, + param_ids_generated: Optional[Sequence[str]] = None, + *, + _ispytest: bool = False, + ) -> None: + """:meta private:""" + check_ispytest(_ispytest) + # Weirdness to bypass frozen=True. + object.__setattr__(self, "name", name) + object.__setattr__(self, "args", args) + object.__setattr__(self, "kwargs", kwargs) + object.__setattr__(self, "_param_ids_from", param_ids_from) + object.__setattr__(self, "_param_ids_generated", param_ids_generated) def _has_param_ids(self) -> bool: return "ids" in self.kwargs or len(self.args) >= 4 @@ -243,6 +261,7 @@ def combined_with(self, other: "Mark") -> "Mark": self.args + other.args, dict(self.kwargs, **other.kwargs), param_ids_from=param_ids_from, + _ispytest=True, ) @@ -320,7 +339,7 @@ def with_args(self, *args: object, **kwargs: object) -> "MarkDecorator": :rtype: MarkDecorator """ - mark = Mark(self.name, args, kwargs) + mark = Mark(self.name, args, kwargs, _ispytest=True) return self.__class__(self.mark.combined_with(mark)) # Type ignored because the overloads overlap with an incompatible @@ -515,7 +534,7 @@ def __getattr__(self, name: str) -> MarkDecorator: 2, ) - return MarkDecorator(Mark(name, (), {})) + return MarkDecorator(Mark(name, (), {}, _ispytest=True)) MARK_GEN = MarkGenerator() diff --git a/src/pytest/__init__.py b/src/pytest/__init__.py index 70177f95040..4b194e0c8e0 100644 --- a/src/pytest/__init__.py +++ b/src/pytest/__init__.py @@ -21,6 +21,7 @@ from _pytest.freeze_support import freeze_includes from _pytest.logging import LogCaptureFixture from _pytest.main import Session +from _pytest.mark import Mark from _pytest.mark import MARK_GEN as mark from _pytest.mark import param from _pytest.monkeypatch import MonkeyPatch @@ -89,6 +90,7 @@ "LogCaptureFixture", "main", "mark", + "Mark", "Module", "MonkeyPatch", "Package", From 69c302479e3f76450f29e7d2de24254d5eda6492 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 20 Dec 2020 15:11:01 +0200 Subject: [PATCH 0031/2772] mark: export pytest.MarkDecorator for typing purposes The type cannot be constructed directly, but is exported for use in type annotations, since it is reachable through existing public API. --- changelog/7469.deprecation.rst | 1 + changelog/7469.feature.rst | 1 + doc/en/reference.rst | 2 +- src/_pytest/fixtures.py | 2 +- src/_pytest/mark/structures.py | 40 +++++++++++++++++++--------------- src/pytest/__init__.py | 2 ++ 6 files changed, 28 insertions(+), 20 deletions(-) diff --git a/changelog/7469.deprecation.rst b/changelog/7469.deprecation.rst index 79419dacef2..6922b3bbb17 100644 --- a/changelog/7469.deprecation.rst +++ b/changelog/7469.deprecation.rst @@ -1,5 +1,6 @@ Directly constructing the following classes is now deprecated: - ``_pytest.mark.structures.Mark`` +- ``_pytest.mark.structures.MarkDecorator`` These have always been considered private, but now issue a deprecation warning, which may become a hard error in pytest 7.0.0. diff --git a/changelog/7469.feature.rst b/changelog/7469.feature.rst index f4bc52414bc..66113aa580f 100644 --- a/changelog/7469.feature.rst +++ b/changelog/7469.feature.rst @@ -3,6 +3,7 @@ The types of objects used in pytest's API are now exported so they may be used i The newly-exported types are: - ``pytest.Mark`` for :class:`marks `. +- ``pytest.MarkDecorator`` for :class:`mark decorators `. Constructing them directly is not supported; they are only meant for use in type annotations. Doing so will emit a deprecation warning, and may become a hard-error in pytest 7.0. diff --git a/doc/en/reference.rst b/doc/en/reference.rst index 3fc62ee7279..8bd4111a1c0 100644 --- a/doc/en/reference.rst +++ b/doc/en/reference.rst @@ -849,7 +849,7 @@ Item MarkDecorator ~~~~~~~~~~~~~ -.. autoclass:: _pytest.mark.MarkDecorator +.. autoclass:: pytest.MarkDecorator() :members: diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index c24ab7069cb..dbb039bf2a9 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -551,7 +551,7 @@ def applymarker(self, marker: Union[str, MarkDecorator]) -> None: on all function invocations. :param marker: - A :py:class:`_pytest.mark.MarkDecorator` object created by a call + A :class:`pytest.MarkDecorator` object created by a call to ``pytest.mark.NAME(...)``. """ self.node.add_marker(marker) diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index 29b9586871f..8bce33e685a 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -268,14 +268,14 @@ def combined_with(self, other: "Mark") -> "Mark": # A generic parameter designating an object to which a Mark may # be applied -- a test function (callable) or class. # Note: a lambda is not allowed, but this can't be represented. -_Markable = TypeVar("_Markable", bound=Union[Callable[..., object], type]) +Markable = TypeVar("Markable", bound=Union[Callable[..., object], type]) -@attr.s +@attr.s(init=False, auto_attribs=True) class MarkDecorator: """A decorator for applying a mark on test functions and classes. - MarkDecorators are created with ``pytest.mark``:: + ``MarkDecorators`` are created with ``pytest.mark``:: mark1 = pytest.mark.NAME # Simple MarkDecorator mark2 = pytest.mark.NAME(name1=value) # Parametrized MarkDecorator @@ -286,7 +286,7 @@ class MarkDecorator: def test_function(): pass - When a MarkDecorator is called it does the following: + When a ``MarkDecorator`` is called, it does the following: 1. If called with a single class as its only positional argument and no additional keyword arguments, it attaches the mark to the class so it @@ -295,19 +295,24 @@ def test_function(): 2. If called with a single function as its only positional argument and no additional keyword arguments, it attaches the mark to the function, containing all the arguments already stored internally in the - MarkDecorator. + ``MarkDecorator``. - 3. When called in any other case, it returns a new MarkDecorator instance - with the original MarkDecorator's content updated with the arguments - passed to this call. + 3. When called in any other case, it returns a new ``MarkDecorator`` + instance with the original ``MarkDecorator``'s content updated with + the arguments passed to this call. - Note: The rules above prevent MarkDecorators from storing only a single - function or class reference as their positional argument with no + Note: The rules above prevent a ``MarkDecorator`` from storing only a + single function or class reference as its positional argument with no additional keyword or positional arguments. You can work around this by using `with_args()`. """ - mark = attr.ib(type=Mark, validator=attr.validators.instance_of(Mark)) + mark: Mark + + def __init__(self, mark: Mark, *, _ispytest: bool = False) -> None: + """:meta private:""" + check_ispytest(_ispytest) + self.mark = mark @property def name(self) -> str: @@ -326,6 +331,7 @@ def kwargs(self) -> Mapping[str, Any]: @property def markname(self) -> str: + """:meta private:""" return self.name # for backward-compat (2.4.1 had this attr) def __repr__(self) -> str: @@ -336,17 +342,15 @@ def with_args(self, *args: object, **kwargs: object) -> "MarkDecorator": Unlike calling the MarkDecorator, with_args() can be used even if the sole argument is a callable/class. - - :rtype: MarkDecorator """ mark = Mark(self.name, args, kwargs, _ispytest=True) - return self.__class__(self.mark.combined_with(mark)) + return MarkDecorator(self.mark.combined_with(mark), _ispytest=True) # Type ignored because the overloads overlap with an incompatible # return type. Not much we can do about that. Thankfully mypy picks # the first match so it works out even if we break the rules. @overload - def __call__(self, arg: _Markable) -> _Markable: # type: ignore[misc] + def __call__(self, arg: Markable) -> Markable: # type: ignore[misc] pass @overload @@ -405,7 +409,7 @@ def store_mark(obj, mark: Mark) -> None: class _SkipMarkDecorator(MarkDecorator): @overload # type: ignore[override,misc] - def __call__(self, arg: _Markable) -> _Markable: + def __call__(self, arg: Markable) -> Markable: ... @overload @@ -423,7 +427,7 @@ def __call__( # type: ignore[override] class _XfailMarkDecorator(MarkDecorator): @overload # type: ignore[override,misc] - def __call__(self, arg: _Markable) -> _Markable: + def __call__(self, arg: Markable) -> Markable: ... @overload @@ -534,7 +538,7 @@ def __getattr__(self, name: str) -> MarkDecorator: 2, ) - return MarkDecorator(Mark(name, (), {}, _ispytest=True)) + return MarkDecorator(Mark(name, (), {}, _ispytest=True), _ispytest=True) MARK_GEN = MarkGenerator() diff --git a/src/pytest/__init__.py b/src/pytest/__init__.py index 4b194e0c8e0..1d5b38ee091 100644 --- a/src/pytest/__init__.py +++ b/src/pytest/__init__.py @@ -23,6 +23,7 @@ from _pytest.main import Session from _pytest.mark import Mark from _pytest.mark import MARK_GEN as mark +from _pytest.mark import MarkDecorator from _pytest.mark import param from _pytest.monkeypatch import MonkeyPatch from _pytest.nodes import Collector @@ -91,6 +92,7 @@ "main", "mark", "Mark", + "MarkDecorator", "Module", "MonkeyPatch", "Package", From 6aa4d1c7ab968aecf44ad89e568a4515bd7e5343 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 20 Dec 2020 15:36:24 +0200 Subject: [PATCH 0032/2772] mark: export pytest.MarkGenerator for typing purposes The type cannot be constructed directly, but is exported for use in type annotations, since it is reachable through existing public API. --- changelog/7469.deprecation.rst | 1 + changelog/7469.feature.rst | 1 + doc/en/reference.rst | 2 +- src/_pytest/mark/structures.py | 11 +++++++---- src/pytest/__init__.py | 2 ++ testing/test_mark.py | 4 ++-- 6 files changed, 14 insertions(+), 7 deletions(-) diff --git a/changelog/7469.deprecation.rst b/changelog/7469.deprecation.rst index 6922b3bbb17..6bbc8075522 100644 --- a/changelog/7469.deprecation.rst +++ b/changelog/7469.deprecation.rst @@ -2,5 +2,6 @@ Directly constructing the following classes is now deprecated: - ``_pytest.mark.structures.Mark`` - ``_pytest.mark.structures.MarkDecorator`` +- ``_pytest.mark.structures.MarkGenerator`` These have always been considered private, but now issue a deprecation warning, which may become a hard error in pytest 7.0.0. diff --git a/changelog/7469.feature.rst b/changelog/7469.feature.rst index 66113aa580f..81f93d1f7af 100644 --- a/changelog/7469.feature.rst +++ b/changelog/7469.feature.rst @@ -4,6 +4,7 @@ The newly-exported types are: - ``pytest.Mark`` for :class:`marks `. - ``pytest.MarkDecorator`` for :class:`mark decorators `. +- ``pytest.MarkGenerator`` for the :class:`pytest.mark ` singleton. Constructing them directly is not supported; they are only meant for use in type annotations. Doing so will emit a deprecation warning, and may become a hard-error in pytest 7.0. diff --git a/doc/en/reference.rst b/doc/en/reference.rst index 8bd4111a1c0..c8e8dca7561 100644 --- a/doc/en/reference.rst +++ b/doc/en/reference.rst @@ -856,7 +856,7 @@ MarkDecorator MarkGenerator ~~~~~~~~~~~~~ -.. autoclass:: _pytest.mark.MarkGenerator +.. autoclass:: pytest.MarkGenerator() :members: diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index 8bce33e685a..ae6920735a2 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -488,9 +488,6 @@ def test_function(): applies a 'slowtest' :class:`Mark` on ``test_function``. """ - _config: Optional[Config] = None - _markers: Set[str] = set() - # See TYPE_CHECKING above. if TYPE_CHECKING: skip: _SkipMarkDecorator @@ -500,7 +497,13 @@ def test_function(): usefixtures: _UsefixturesMarkDecorator filterwarnings: _FilterwarningsMarkDecorator + def __init__(self, *, _ispytest: bool = False) -> None: + check_ispytest(_ispytest) + self._config: Optional[Config] = None + self._markers: Set[str] = set() + def __getattr__(self, name: str) -> MarkDecorator: + """Generate a new :class:`MarkDecorator` with the given name.""" if name[0] == "_": raise AttributeError("Marker name must NOT start with underscore") @@ -541,7 +544,7 @@ def __getattr__(self, name: str) -> MarkDecorator: return MarkDecorator(Mark(name, (), {}, _ispytest=True), _ispytest=True) -MARK_GEN = MarkGenerator() +MARK_GEN = MarkGenerator(_ispytest=True) @final diff --git a/src/pytest/__init__.py b/src/pytest/__init__.py index 1d5b38ee091..74cf00ee2c4 100644 --- a/src/pytest/__init__.py +++ b/src/pytest/__init__.py @@ -24,6 +24,7 @@ from _pytest.mark import Mark from _pytest.mark import MARK_GEN as mark from _pytest.mark import MarkDecorator +from _pytest.mark import MarkGenerator from _pytest.mark import param from _pytest.monkeypatch import MonkeyPatch from _pytest.nodes import Collector @@ -93,6 +94,7 @@ "mark", "Mark", "MarkDecorator", + "MarkGenerator", "Module", "MonkeyPatch", "Package", diff --git a/testing/test_mark.py b/testing/test_mark.py index e0b91f0cef4..5f4b3e063e4 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -21,7 +21,7 @@ def test_pytest_exists_in_namespace_all(self, attr: str, modulename: str) -> Non assert attr in module.__all__ # type: ignore def test_pytest_mark_notcallable(self) -> None: - mark = MarkGenerator() + mark = MarkGenerator(_ispytest=True) with pytest.raises(TypeError): mark() # type: ignore[operator] @@ -40,7 +40,7 @@ class SomeClass: assert pytest.mark.foo.with_args(SomeClass) is not SomeClass # type: ignore[comparison-overlap] def test_pytest_mark_name_starts_with_underscore(self) -> None: - mark = MarkGenerator() + mark = MarkGenerator(_ispytest=True) with pytest.raises(AttributeError): mark._some_name From 1839713b71db4f9d656c5ae3de32a9abcaa99009 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Dec 2020 03:07:23 +0000 Subject: [PATCH 0033/2772] build(deps): bump pytest-mock in /testing/plugins_integration Bumps [pytest-mock](https://github.com/pytest-dev/pytest-mock) from 3.3.1 to 3.4.0. - [Release notes](https://github.com/pytest-dev/pytest-mock/releases) - [Changelog](https://github.com/pytest-dev/pytest-mock/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-mock/compare/v3.3.1...v3.4.0) Signed-off-by: dependabot[bot] --- testing/plugins_integration/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt index 99ddef0706e..b2ca3e3236a 100644 --- a/testing/plugins_integration/requirements.txt +++ b/testing/plugins_integration/requirements.txt @@ -6,7 +6,7 @@ pytest-cov==2.10.1 pytest-django==4.1.0 pytest-flakes==4.0.3 pytest-html==3.1.1 -pytest-mock==3.3.1 +pytest-mock==3.4.0 pytest-rerunfailures==9.1.1 pytest-sugar==0.9.4 pytest-trio==0.7.0 From 92ba96b0612e6b06bb8f4ab05bd75481d2504806 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 19 Dec 2020 14:11:00 +0200 Subject: [PATCH 0034/2772] code: convert from py.path to pathlib --- changelog/8174.trivial.rst | 5 +++ src/_pytest/_code/code.py | 55 +++++++++++++++------------- src/_pytest/fixtures.py | 8 +++- src/_pytest/nodes.py | 8 ++-- src/_pytest/python.py | 6 ++- testing/code/test_excinfo.py | 71 ++++++++++++++++++------------------ testing/code/test_source.py | 11 +++--- 7 files changed, 90 insertions(+), 74 deletions(-) create mode 100644 changelog/8174.trivial.rst diff --git a/changelog/8174.trivial.rst b/changelog/8174.trivial.rst new file mode 100644 index 00000000000..001ae4cb193 --- /dev/null +++ b/changelog/8174.trivial.rst @@ -0,0 +1,5 @@ +The following changes have been made to internal pytest types/functions: + +- The ``path`` property of ``_pytest.code.Code`` returns ``Path`` instead of ``py.path.local``. +- The ``path`` property of ``_pytest.code.TracebackEntry`` returns ``Path`` instead of ``py.path.local``. +- The ``_pytest.code.getfslineno()`` function returns ``Path`` instead of ``py.path.local``. diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index 423069330a5..043a23a79af 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -43,6 +43,8 @@ from _pytest._io.saferepr import saferepr from _pytest.compat import final from _pytest.compat import get_real_func +from _pytest.pathlib import absolutepath +from _pytest.pathlib import bestrelpath if TYPE_CHECKING: from typing_extensions import Literal @@ -78,16 +80,16 @@ def name(self) -> str: return self.raw.co_name @property - def path(self) -> Union[py.path.local, str]: + def path(self) -> Union[Path, str]: """Return a path object pointing to source code, or an ``str`` in case of ``OSError`` / non-existing file.""" if not self.raw.co_filename: return "" try: - p = py.path.local(self.raw.co_filename) + p = absolutepath(self.raw.co_filename) # maybe don't try this checking - if not p.check(): - raise OSError("py.path check failed.") + if not p.exists(): + raise OSError("path check failed.") return p except OSError: # XXX maybe try harder like the weird logic @@ -223,7 +225,7 @@ def statement(self) -> "Source": return source.getstatement(self.lineno) @property - def path(self) -> Union[py.path.local, str]: + def path(self) -> Union[Path, str]: """Path to the source code.""" return self.frame.code.path @@ -336,10 +338,10 @@ def f(cur: TracebackType) -> Iterable[TracebackEntry]: def cut( self, - path=None, + path: Optional[Union[Path, str]] = None, lineno: Optional[int] = None, firstlineno: Optional[int] = None, - excludepath: Optional[py.path.local] = None, + excludepath: Optional[Path] = None, ) -> "Traceback": """Return a Traceback instance wrapping part of this Traceback. @@ -353,17 +355,19 @@ def cut( for x in self: code = x.frame.code codepath = code.path + if path is not None and codepath != path: + continue if ( - (path is None or codepath == path) - and ( - excludepath is None - or not isinstance(codepath, py.path.local) - or not codepath.relto(excludepath) - ) - and (lineno is None or x.lineno == lineno) - and (firstlineno is None or x.frame.code.firstlineno == firstlineno) + excludepath is not None + and isinstance(codepath, Path) + and excludepath in codepath.parents ): - return Traceback(x._rawentry, self._excinfo) + continue + if lineno is not None and x.lineno != lineno: + continue + if firstlineno is not None and x.frame.code.firstlineno != firstlineno: + continue + return Traceback(x._rawentry, self._excinfo) return self @overload @@ -801,7 +805,8 @@ def repr_traceback_entry( message = "in %s" % (entry.name) else: message = excinfo and excinfo.typename or "" - path = self._makepath(entry.path) + entry_path = entry.path + path = self._makepath(entry_path) reprfileloc = ReprFileLocation(path, entry.lineno + 1, message) localsrepr = self.repr_locals(entry.locals) return ReprEntry(lines, reprargs, localsrepr, reprfileloc, style) @@ -814,15 +819,15 @@ def repr_traceback_entry( lines.extend(self.get_exconly(excinfo, indent=4)) return ReprEntry(lines, None, None, None, style) - def _makepath(self, path): - if not self.abspath: + def _makepath(self, path: Union[Path, str]) -> str: + if not self.abspath and isinstance(path, Path): try: - np = py.path.local().bestrelpath(path) + np = bestrelpath(Path.cwd(), path) except OSError: - return path + return str(path) if len(np) < len(str(path)): - path = np - return path + return np + return str(path) def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> "ReprTraceback": traceback = excinfo.traceback @@ -1181,7 +1186,7 @@ def toterminal(self, tw: TerminalWriter) -> None: tw.line("") -def getfslineno(obj: object) -> Tuple[Union[str, py.path.local], int]: +def getfslineno(obj: object) -> Tuple[Union[str, Path], int]: """Return source location (path, lineno) for the given object. If the source cannot be determined return ("", -1). @@ -1203,7 +1208,7 @@ def getfslineno(obj: object) -> Tuple[Union[str, py.path.local], int]: except TypeError: return "", -1 - fspath = fn and py.path.local(fn) or "" + fspath = fn and absolutepath(fn) or "" lineno = -1 if fspath: try: diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index c24ab7069cb..6db1c5906f0 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -5,6 +5,7 @@ import warnings from collections import defaultdict from collections import deque +from pathlib import Path from types import TracebackType from typing import Any from typing import Callable @@ -58,6 +59,7 @@ from _pytest.outcomes import fail from _pytest.outcomes import TEST_OUTCOME from _pytest.pathlib import absolutepath +from _pytest.pathlib import bestrelpath from _pytest.store import StoreKey if TYPE_CHECKING: @@ -718,7 +720,11 @@ def _factorytraceback(self) -> List[str]: for fixturedef in self._get_fixturestack(): factory = fixturedef.func fs, lineno = getfslineno(factory) - p = self._pyfuncitem.session.fspath.bestrelpath(fs) + if isinstance(fs, Path): + session: Session = self._pyfuncitem.session + p = bestrelpath(Path(session.fspath), fs) + else: + p = fs args = _format_args(factory) lines.append("%s:%d: def %s%s" % (p, lineno + 1, factory.__name__, args)) return lines diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 1b3ec5571b1..da2a0a7ea79 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -39,7 +39,7 @@ SEP = "/" -tracebackcutdir = py.path.local(_pytest.__file__).dirpath() +tracebackcutdir = Path(_pytest.__file__).parent def iterparentnodeids(nodeid: str) -> Iterator[str]: @@ -416,9 +416,7 @@ def repr_failure( return self._repr_failure_py(excinfo, style) -def get_fslocation_from_item( - node: "Node", -) -> Tuple[Union[str, py.path.local], Optional[int]]: +def get_fslocation_from_item(node: "Node") -> Tuple[Union[str, Path], Optional[int]]: """Try to extract the actual location from a node, depending on available attributes: * "location": a pair (path, lineno) @@ -474,7 +472,7 @@ def repr_failure( # type: ignore[override] def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None: if hasattr(self, "fspath"): traceback = excinfo.traceback - ntraceback = traceback.cut(path=self.fspath) + ntraceback = traceback.cut(path=Path(self.fspath)) if ntraceback == traceback: ntraceback = ntraceback.cut(excludepath=tracebackcutdir) excinfo.traceback = ntraceback.filter() diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 3ff04455fbf..27bbb24fe2c 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -340,7 +340,11 @@ def reportinfo(self) -> Tuple[Union[py.path.local, str], int, str]: fspath: Union[py.path.local, str] = file_path lineno = compat_co_firstlineno else: - fspath, lineno = getfslineno(obj) + path, lineno = getfslineno(obj) + if isinstance(path, Path): + fspath = py.path.local(path) + else: + fspath = path modpath = self.getmodpath() assert isinstance(lineno, int) return fspath, lineno, modpath diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index 44d7ab549e8..19c888403f2 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -1,7 +1,6 @@ import importlib import io import operator -import os import queue import sys import textwrap @@ -12,14 +11,14 @@ from typing import TYPE_CHECKING from typing import Union -import py - import _pytest import pytest from _pytest._code.code import ExceptionChainRepr from _pytest._code.code import ExceptionInfo from _pytest._code.code import FormattedExcinfo from _pytest._io import TerminalWriter +from _pytest.monkeypatch import MonkeyPatch +from _pytest.pathlib import bestrelpath from _pytest.pathlib import import_path from _pytest.pytester import LineMatcher from _pytest.pytester import Pytester @@ -150,9 +149,10 @@ def xyz(): " except somenoname: # type: ignore[name-defined] # noqa: F821", ] - def test_traceback_cut(self): + def test_traceback_cut(self) -> None: co = _pytest._code.Code.from_function(f) path, firstlineno = co.path, co.firstlineno + assert isinstance(path, Path) traceback = self.excinfo.traceback newtraceback = traceback.cut(path=path, firstlineno=firstlineno) assert len(newtraceback) == 1 @@ -163,11 +163,11 @@ def test_traceback_cut_excludepath(self, pytester: Pytester) -> None: p = pytester.makepyfile("def f(): raise ValueError") with pytest.raises(ValueError) as excinfo: import_path(p).f() # type: ignore[attr-defined] - basedir = py.path.local(pytest.__file__).dirpath() + basedir = Path(pytest.__file__).parent newtraceback = excinfo.traceback.cut(excludepath=basedir) for x in newtraceback: - if hasattr(x, "path"): - assert not py.path.local(x.path).relto(basedir) + assert isinstance(x.path, Path) + assert basedir not in x.path.parents assert newtraceback[-1].frame.code.path == p def test_traceback_filter(self): @@ -376,7 +376,7 @@ def test_excinfo_no_python_sourcecode(tmpdir): for item in excinfo.traceback: print(item) # XXX: for some reason jinja.Template.render is printed in full item.source # shouldn't fail - if isinstance(item.path, py.path.local) and item.path.basename == "test.txt": + if isinstance(item.path, Path) and item.path.name == "test.txt": assert str(item.source) == "{{ h()}}:" @@ -392,16 +392,16 @@ def test_entrysource_Queue_example(): assert s.startswith("def get") -def test_codepath_Queue_example(): +def test_codepath_Queue_example() -> None: try: queue.Queue().get(timeout=0.001) except queue.Empty: excinfo = _pytest._code.ExceptionInfo.from_current() entry = excinfo.traceback[-1] path = entry.path - assert isinstance(path, py.path.local) - assert path.basename.lower() == "queue.py" - assert path.check() + assert isinstance(path, Path) + assert path.name.lower() == "queue.py" + assert path.exists() def test_match_succeeds(): @@ -805,21 +805,21 @@ def entry(): raised = 0 - orig_getcwd = os.getcwd + orig_path_cwd = Path.cwd def raiseos(): nonlocal raised upframe = sys._getframe().f_back assert upframe is not None - if upframe.f_code.co_name == "checked_call": + if upframe.f_code.co_name == "_makepath": # Only raise with expected calls, but not via e.g. inspect for # py38-windows. raised += 1 raise OSError(2, "custom_oserror") - return orig_getcwd() + return orig_path_cwd() - monkeypatch.setattr(os, "getcwd", raiseos) - assert p._makepath(__file__) == __file__ + monkeypatch.setattr(Path, "cwd", raiseos) + assert p._makepath(Path(__file__)) == __file__ assert raised == 1 repr_tb = p.repr_traceback(excinfo) @@ -1015,7 +1015,9 @@ def f(): assert line.endswith("mod.py") assert tw_mock.lines[10] == ":3: ValueError" - def test_toterminal_long_filenames(self, importasmod, tw_mock): + def test_toterminal_long_filenames( + self, importasmod, tw_mock, monkeypatch: MonkeyPatch + ) -> None: mod = importasmod( """ def f(): @@ -1023,25 +1025,22 @@ def f(): """ ) excinfo = pytest.raises(ValueError, mod.f) - path = py.path.local(mod.__file__) - old = path.dirpath().chdir() - try: - repr = excinfo.getrepr(abspath=False) - repr.toterminal(tw_mock) - x = py.path.local().bestrelpath(path) - if len(x) < len(str(path)): - msg = tw_mock.get_write_msg(-2) - assert msg == "mod.py" - assert tw_mock.lines[-1] == ":3: ValueError" - - repr = excinfo.getrepr(abspath=True) - repr.toterminal(tw_mock) + path = Path(mod.__file__) + monkeypatch.chdir(path.parent) + repr = excinfo.getrepr(abspath=False) + repr.toterminal(tw_mock) + x = bestrelpath(Path.cwd(), path) + if len(x) < len(str(path)): msg = tw_mock.get_write_msg(-2) - assert msg == path - line = tw_mock.lines[-1] - assert line == ":3: ValueError" - finally: - old.chdir() + assert msg == "mod.py" + assert tw_mock.lines[-1] == ":3: ValueError" + + repr = excinfo.getrepr(abspath=True) + repr.toterminal(tw_mock) + msg = tw_mock.get_write_msg(-2) + assert msg == str(path) + line = tw_mock.lines[-1] + assert line == ":3: ValueError" @pytest.mark.parametrize( "reproptions", diff --git a/testing/code/test_source.py b/testing/code/test_source.py index 04d0ea9323d..6b8443fd243 100644 --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -6,13 +6,12 @@ import linecache import sys import textwrap +from pathlib import Path from types import CodeType from typing import Any from typing import Dict from typing import Optional -import py.path - import pytest from _pytest._code import Code from _pytest._code import Frame @@ -352,8 +351,8 @@ def f(x) -> None: fspath, lineno = getfslineno(f) - assert isinstance(fspath, py.path.local) - assert fspath.basename == "test_source.py" + assert isinstance(fspath, Path) + assert fspath.name == "test_source.py" assert lineno == f.__code__.co_firstlineno - 1 # see findsource class A: @@ -362,8 +361,8 @@ class A: fspath, lineno = getfslineno(A) _, A_lineno = inspect.findsource(A) - assert isinstance(fspath, py.path.local) - assert fspath.basename == "test_source.py" + assert isinstance(fspath, Path) + assert fspath.name == "test_source.py" assert lineno == A_lineno assert getfslineno(3) == ("", -1) From 8b220fad4de5e36d3b62d57ca0121b4865f7e518 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 19 Dec 2020 14:48:56 +0200 Subject: [PATCH 0035/2772] testing/test_helpconfig: remove unclear comment --- testing/test_helpconfig.py | 1 - 1 file changed, 1 deletion(-) diff --git a/testing/test_helpconfig.py b/testing/test_helpconfig.py index c2533ef304a..9a433b1b17e 100644 --- a/testing/test_helpconfig.py +++ b/testing/test_helpconfig.py @@ -16,7 +16,6 @@ def test_version_less_verbose(pytester: Pytester, pytestconfig, monkeypatch) -> monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD") result = pytester.runpytest("--version") assert result.ret == 0 - # p = py.path.local(py.__file__).dirpath() result.stderr.fnmatch_lines([f"pytest {pytest.__version__}"]) From 170a2c50408c551c31dbe51e9572c174d0c5cdde Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 19 Dec 2020 14:49:24 +0200 Subject: [PATCH 0036/2772] testing/test_config: check inipath instead of inifile inifile is soft-deprecated in favor of inipath. --- testing/test_config.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/testing/test_config.py b/testing/test_config.py index eacc9c9ebdd..8c1441e0680 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -11,7 +11,6 @@ from typing import Union import attr -import py.path import _pytest._code import pytest @@ -28,6 +27,7 @@ from _pytest.config.findpaths import get_common_ancestor from _pytest.config.findpaths import locate_config from _pytest.monkeypatch import MonkeyPatch +from _pytest.pathlib import absolutepath from _pytest.pytester import Pytester @@ -854,8 +854,8 @@ def test_inifilename(self, tmp_path: Path) -> None: ) ) - inifile = "../../foo/bar.ini" - option_dict = {"inifilename": inifile, "capture": "no"} + inifilename = "../../foo/bar.ini" + option_dict = {"inifilename": inifilename, "capture": "no"} cwd = tmp_path.joinpath("a/b") cwd.mkdir(parents=True) @@ -873,14 +873,14 @@ def test_inifilename(self, tmp_path: Path) -> None: with MonkeyPatch.context() as mp: mp.chdir(cwd) config = Config.fromdictargs(option_dict, ()) - inipath = py.path.local(inifile) + inipath = absolutepath(inifilename) assert config.args == [str(cwd)] - assert config.option.inifilename == inifile + assert config.option.inifilename == inifilename assert config.option.capture == "no" # this indicates this is the file used for getting configuration values - assert config.inifile == inipath + assert config.inipath == inipath assert config.inicfg.get("name") == "value" assert config.inicfg.get("should_not_be_set") is None From a21841300826fd67b43e5bb3ff1a04e11759dcc1 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 19 Dec 2020 14:51:23 +0200 Subject: [PATCH 0037/2772] pathlib: missing type annotation for fnmatch_ex --- src/_pytest/pathlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index 2e452eb1cc9..d3908a3fdc0 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -387,7 +387,7 @@ def resolve_from_str(input: str, rootpath: Path) -> Path: return rootpath.joinpath(input) -def fnmatch_ex(pattern: str, path) -> bool: +def fnmatch_ex(pattern: str, path: Union[str, "os.PathLike[str]"]) -> bool: """A port of FNMatcher from py.path.common which works with PurePath() instances. The difference between this algorithm and PurePath.match() is that the From 4faed282613dbbe7196543cc8b45885269f39d4a Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 19 Dec 2020 15:16:01 +0200 Subject: [PATCH 0038/2772] testing: convert some tmpdir to tmp_path The tmpdir fixture (and its factory variant) is soft-deprecated in favor of the tmp_path fixture. --- testing/test_cacheprovider.py | 15 ++- testing/test_pathlib.py | 204 +++++++++++++++++++--------------- 2 files changed, 124 insertions(+), 95 deletions(-) diff --git a/testing/test_cacheprovider.py b/testing/test_cacheprovider.py index ebd455593f3..2cb657efc16 100644 --- a/testing/test_cacheprovider.py +++ b/testing/test_cacheprovider.py @@ -8,6 +8,7 @@ from _pytest.config import ExitCode from _pytest.monkeypatch import MonkeyPatch from _pytest.pytester import Pytester +from _pytest.tmpdir import TempPathFactory pytest_plugins = ("pytester",) @@ -139,9 +140,11 @@ def test_custom_rel_cache_dir(self, pytester: Pytester) -> None: pytester.runpytest() assert pytester.path.joinpath(rel_cache_dir).is_dir() - def test_custom_abs_cache_dir(self, pytester: Pytester, tmpdir_factory) -> None: - tmp = str(tmpdir_factory.mktemp("tmp")) - abs_cache_dir = os.path.join(tmp, "custom_cache_dir") + def test_custom_abs_cache_dir( + self, pytester: Pytester, tmp_path_factory: TempPathFactory + ) -> None: + tmp = tmp_path_factory.mktemp("tmp") + abs_cache_dir = tmp / "custom_cache_dir" pytester.makeini( """ [pytest] @@ -152,7 +155,7 @@ def test_custom_abs_cache_dir(self, pytester: Pytester, tmpdir_factory) -> None: ) pytester.makepyfile(test_errored="def test_error():\n assert False") pytester.runpytest() - assert Path(abs_cache_dir).is_dir() + assert abs_cache_dir.is_dir() def test_custom_cache_dir_with_env_var( self, pytester: Pytester, monkeypatch: MonkeyPatch @@ -185,9 +188,9 @@ def test_cache_reportheader(env, pytester: Pytester, monkeypatch: MonkeyPatch) - def test_cache_reportheader_external_abspath( - pytester: Pytester, tmpdir_factory + pytester: Pytester, tmp_path_factory: TempPathFactory ) -> None: - external_cache = tmpdir_factory.mktemp( + external_cache = tmp_path_factory.mktemp( "test_cache_reportheader_external_abspath_abs" ) diff --git a/testing/test_pathlib.py b/testing/test_pathlib.py index f60b9f26369..48149084ece 100644 --- a/testing/test_pathlib.py +++ b/testing/test_pathlib.py @@ -1,8 +1,11 @@ import os.path +import pickle import sys import unittest.mock from pathlib import Path from textwrap import dedent +from types import ModuleType +from typing import Generator import py @@ -20,6 +23,7 @@ from _pytest.pathlib import resolve_package_path from _pytest.pathlib import symlink_or_skip from _pytest.pathlib import visit +from _pytest.tmpdir import TempPathFactory class TestFNMatcherPort: @@ -96,38 +100,40 @@ class TestImportPath: """ @pytest.fixture(scope="session") - def path1(self, tmpdir_factory): - path = tmpdir_factory.mktemp("path") + def path1(self, tmp_path_factory: TempPathFactory) -> Generator[Path, None, None]: + path = tmp_path_factory.mktemp("path") self.setuptestfs(path) yield path - assert path.join("samplefile").check() + assert path.joinpath("samplefile").exists() - def setuptestfs(self, path): + def setuptestfs(self, path: Path) -> None: # print "setting up test fs for", repr(path) - samplefile = path.ensure("samplefile") - samplefile.write("samplefile\n") + samplefile = path / "samplefile" + samplefile.write_text("samplefile\n") - execfile = path.ensure("execfile") - execfile.write("x=42") + execfile = path / "execfile" + execfile.write_text("x=42") - execfilepy = path.ensure("execfile.py") - execfilepy.write("x=42") + execfilepy = path / "execfile.py" + execfilepy.write_text("x=42") d = {1: 2, "hello": "world", "answer": 42} - path.ensure("samplepickle").dump(d) - - sampledir = path.ensure("sampledir", dir=1) - sampledir.ensure("otherfile") - - otherdir = path.ensure("otherdir", dir=1) - otherdir.ensure("__init__.py") - - module_a = otherdir.ensure("a.py") - module_a.write("from .b import stuff as result\n") - module_b = otherdir.ensure("b.py") - module_b.write('stuff="got it"\n') - module_c = otherdir.ensure("c.py") - module_c.write( + path.joinpath("samplepickle").write_bytes(pickle.dumps(d, 1)) + + sampledir = path / "sampledir" + sampledir.mkdir() + sampledir.joinpath("otherfile").touch() + + otherdir = path / "otherdir" + otherdir.mkdir() + otherdir.joinpath("__init__.py").touch() + + module_a = otherdir / "a.py" + module_a.write_text("from .b import stuff as result\n") + module_b = otherdir / "b.py" + module_b.write_text('stuff="got it"\n') + module_c = otherdir / "c.py" + module_c.write_text( dedent( """ import py; @@ -136,8 +142,8 @@ def setuptestfs(self, path): """ ) ) - module_d = otherdir.ensure("d.py") - module_d.write( + module_d = otherdir / "d.py" + module_d.write_text( dedent( """ import py; @@ -147,122 +153,141 @@ def setuptestfs(self, path): ) ) - def test_smoke_test(self, path1): - obj = import_path(path1.join("execfile.py")) + def test_smoke_test(self, path1: Path) -> None: + obj = import_path(path1 / "execfile.py") assert obj.x == 42 # type: ignore[attr-defined] assert obj.__name__ == "execfile" - def test_renamed_dir_creates_mismatch(self, tmpdir, monkeypatch): - p = tmpdir.ensure("a", "test_x123.py") + def test_renamed_dir_creates_mismatch( + self, tmp_path: Path, monkeypatch: MonkeyPatch + ) -> None: + tmp_path.joinpath("a").mkdir() + p = tmp_path.joinpath("a", "test_x123.py") + p.touch() import_path(p) - tmpdir.join("a").move(tmpdir.join("b")) + tmp_path.joinpath("a").rename(tmp_path.joinpath("b")) with pytest.raises(ImportPathMismatchError): - import_path(tmpdir.join("b", "test_x123.py")) + import_path(tmp_path.joinpath("b", "test_x123.py")) # Errors can be ignored. monkeypatch.setenv("PY_IGNORE_IMPORTMISMATCH", "1") - import_path(tmpdir.join("b", "test_x123.py")) + import_path(tmp_path.joinpath("b", "test_x123.py")) # PY_IGNORE_IMPORTMISMATCH=0 does not ignore error. monkeypatch.setenv("PY_IGNORE_IMPORTMISMATCH", "0") with pytest.raises(ImportPathMismatchError): - import_path(tmpdir.join("b", "test_x123.py")) + import_path(tmp_path.joinpath("b", "test_x123.py")) - def test_messy_name(self, tmpdir): + def test_messy_name(self, tmp_path: Path) -> None: # http://bitbucket.org/hpk42/py-trunk/issue/129 - path = tmpdir.ensure("foo__init__.py") + path = tmp_path / "foo__init__.py" + path.touch() module = import_path(path) assert module.__name__ == "foo__init__" - def test_dir(self, tmpdir): - p = tmpdir.join("hello_123") - p_init = p.ensure("__init__.py") + def test_dir(self, tmp_path: Path) -> None: + p = tmp_path / "hello_123" + p.mkdir() + p_init = p / "__init__.py" + p_init.touch() m = import_path(p) assert m.__name__ == "hello_123" m = import_path(p_init) assert m.__name__ == "hello_123" - def test_a(self, path1): - otherdir = path1.join("otherdir") - mod = import_path(otherdir.join("a.py")) + def test_a(self, path1: Path) -> None: + otherdir = path1 / "otherdir" + mod = import_path(otherdir / "a.py") assert mod.result == "got it" # type: ignore[attr-defined] assert mod.__name__ == "otherdir.a" - def test_b(self, path1): - otherdir = path1.join("otherdir") - mod = import_path(otherdir.join("b.py")) + def test_b(self, path1: Path) -> None: + otherdir = path1 / "otherdir" + mod = import_path(otherdir / "b.py") assert mod.stuff == "got it" # type: ignore[attr-defined] assert mod.__name__ == "otherdir.b" - def test_c(self, path1): - otherdir = path1.join("otherdir") - mod = import_path(otherdir.join("c.py")) + def test_c(self, path1: Path) -> None: + otherdir = path1 / "otherdir" + mod = import_path(otherdir / "c.py") assert mod.value == "got it" # type: ignore[attr-defined] - def test_d(self, path1): - otherdir = path1.join("otherdir") - mod = import_path(otherdir.join("d.py")) + def test_d(self, path1: Path) -> None: + otherdir = path1 / "otherdir" + mod = import_path(otherdir / "d.py") assert mod.value2 == "got it" # type: ignore[attr-defined] - def test_import_after(self, tmpdir): - tmpdir.ensure("xxxpackage", "__init__.py") - mod1path = tmpdir.ensure("xxxpackage", "module1.py") + def test_import_after(self, tmp_path: Path) -> None: + tmp_path.joinpath("xxxpackage").mkdir() + tmp_path.joinpath("xxxpackage", "__init__.py").touch() + mod1path = tmp_path.joinpath("xxxpackage", "module1.py") + mod1path.touch() mod1 = import_path(mod1path) assert mod1.__name__ == "xxxpackage.module1" from xxxpackage import module1 assert module1 is mod1 - def test_check_filepath_consistency(self, monkeypatch, tmpdir): + def test_check_filepath_consistency( + self, monkeypatch: MonkeyPatch, tmp_path: Path + ) -> None: name = "pointsback123" - ModuleType = type(os) - p = tmpdir.ensure(name + ".py") + p = tmp_path.joinpath(name + ".py") + p.touch() for ending in (".pyc", ".pyo"): mod = ModuleType(name) - pseudopath = tmpdir.ensure(name + ending) + pseudopath = tmp_path.joinpath(name + ending) + pseudopath.touch() mod.__file__ = str(pseudopath) monkeypatch.setitem(sys.modules, name, mod) newmod = import_path(p) assert mod == newmod monkeypatch.undo() mod = ModuleType(name) - pseudopath = tmpdir.ensure(name + "123.py") + pseudopath = tmp_path.joinpath(name + "123.py") + pseudopath.touch() mod.__file__ = str(pseudopath) monkeypatch.setitem(sys.modules, name, mod) with pytest.raises(ImportPathMismatchError) as excinfo: import_path(p) modname, modfile, orig = excinfo.value.args assert modname == name - assert modfile == pseudopath + assert modfile == str(pseudopath) assert orig == p assert issubclass(ImportPathMismatchError, ImportError) - def test_issue131_on__init__(self, tmpdir): + def test_issue131_on__init__(self, tmp_path: Path) -> None: # __init__.py files may be namespace packages, and thus the # __file__ of an imported module may not be ourselves # see issue - p1 = tmpdir.ensure("proja", "__init__.py") - p2 = tmpdir.ensure("sub", "proja", "__init__.py") + tmp_path.joinpath("proja").mkdir() + p1 = tmp_path.joinpath("proja", "__init__.py") + p1.touch() + tmp_path.joinpath("sub", "proja").mkdir(parents=True) + p2 = tmp_path.joinpath("sub", "proja", "__init__.py") + p2.touch() m1 = import_path(p1) m2 = import_path(p2) assert m1 == m2 - def test_ensuresyspath_append(self, tmpdir): - root1 = tmpdir.mkdir("root1") - file1 = root1.ensure("x123.py") + def test_ensuresyspath_append(self, tmp_path: Path) -> None: + root1 = tmp_path / "root1" + root1.mkdir() + file1 = root1 / "x123.py" + file1.touch() assert str(root1) not in sys.path import_path(file1, mode="append") assert str(root1) == sys.path[-1] assert str(root1) not in sys.path[:-1] - def test_invalid_path(self, tmpdir): + def test_invalid_path(self, tmp_path: Path) -> None: with pytest.raises(ImportError): - import_path(tmpdir.join("invalid.py")) + import_path(tmp_path / "invalid.py") @pytest.fixture - def simple_module(self, tmpdir): - fn = tmpdir.join("mymod.py") - fn.write( + def simple_module(self, tmp_path: Path) -> Path: + fn = tmp_path / "mymod.py" + fn.write_text( dedent( """ def foo(x): return 40 + x @@ -271,19 +296,21 @@ def foo(x): return 40 + x ) return fn - def test_importmode_importlib(self, simple_module): + def test_importmode_importlib(self, simple_module: Path) -> None: """`importlib` mode does not change sys.path.""" module = import_path(simple_module, mode="importlib") assert module.foo(2) == 42 # type: ignore[attr-defined] - assert simple_module.dirname not in sys.path + assert str(simple_module.parent) not in sys.path - def test_importmode_twice_is_different_module(self, simple_module): + def test_importmode_twice_is_different_module(self, simple_module: Path) -> None: """`importlib` mode always returns a new module.""" module1 = import_path(simple_module, mode="importlib") module2 = import_path(simple_module, mode="importlib") assert module1 is not module2 - def test_no_meta_path_found(self, simple_module, monkeypatch): + def test_no_meta_path_found( + self, simple_module: Path, monkeypatch: MonkeyPatch + ) -> None: """Even without any meta_path should still import module.""" monkeypatch.setattr(sys, "meta_path", []) module = import_path(simple_module, mode="importlib") @@ -299,7 +326,7 @@ def test_no_meta_path_found(self, simple_module, monkeypatch): import_path(simple_module, mode="importlib") -def test_resolve_package_path(tmp_path): +def test_resolve_package_path(tmp_path: Path) -> None: pkg = tmp_path / "pkg1" pkg.mkdir() (pkg / "__init__.py").touch() @@ -309,7 +336,7 @@ def test_resolve_package_path(tmp_path): assert resolve_package_path(pkg.joinpath("subdir", "__init__.py")) == pkg -def test_package_unimportable(tmp_path): +def test_package_unimportable(tmp_path: Path) -> None: pkg = tmp_path / "pkg1-1" pkg.mkdir() pkg.joinpath("__init__.py").touch() @@ -323,7 +350,7 @@ def test_package_unimportable(tmp_path): assert not resolve_package_path(pkg) -def test_access_denied_during_cleanup(tmp_path, monkeypatch): +def test_access_denied_during_cleanup(tmp_path: Path, monkeypatch: MonkeyPatch) -> None: """Ensure that deleting a numbered dir does not fail because of OSErrors (#4262).""" path = tmp_path / "temp-1" path.mkdir() @@ -338,7 +365,7 @@ def renamed_failed(*args): assert not lock_path.is_file() -def test_long_path_during_cleanup(tmp_path): +def test_long_path_during_cleanup(tmp_path: Path) -> None: """Ensure that deleting long path works (particularly on Windows (#6775)).""" path = (tmp_path / ("a" * 250)).resolve() if sys.platform == "win32": @@ -354,14 +381,14 @@ def test_long_path_during_cleanup(tmp_path): assert not os.path.isdir(extended_path) -def test_get_extended_length_path_str(): +def test_get_extended_length_path_str() -> None: assert get_extended_length_path_str(r"c:\foo") == r"\\?\c:\foo" assert get_extended_length_path_str(r"\\share\foo") == r"\\?\UNC\share\foo" assert get_extended_length_path_str(r"\\?\UNC\share\foo") == r"\\?\UNC\share\foo" assert get_extended_length_path_str(r"\\?\c:\foo") == r"\\?\c:\foo" -def test_suppress_error_removing_lock(tmp_path): +def test_suppress_error_removing_lock(tmp_path: Path) -> None: """ensure_deletable should be resilient if lock file cannot be removed (#5456, #7491)""" path = tmp_path / "dir" path.mkdir() @@ -406,15 +433,14 @@ def test_commonpath() -> None: assert commonpath(path, path.parent.parent) == path.parent.parent -def test_visit_ignores_errors(tmpdir) -> None: - symlink_or_skip("recursive", tmpdir.join("recursive")) - tmpdir.join("foo").write_binary(b"") - tmpdir.join("bar").write_binary(b"") +def test_visit_ignores_errors(tmp_path: Path) -> None: + symlink_or_skip("recursive", tmp_path / "recursive") + tmp_path.joinpath("foo").write_bytes(b"") + tmp_path.joinpath("bar").write_bytes(b"") - assert [entry.name for entry in visit(tmpdir, recurse=lambda entry: False)] == [ - "bar", - "foo", - ] + assert [ + entry.name for entry in visit(str(tmp_path), recurse=lambda entry: False) + ] == ["bar", "foo"] @pytest.mark.skipif(not sys.platform.startswith("win"), reason="Windows only") From ca4effc8225edf7fc828a4291642c82349ed8107 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 19 Dec 2020 14:52:10 +0200 Subject: [PATCH 0039/2772] Convert most of the collection code from py.path to pathlib --- changelog/8174.trivial.rst | 1 + src/_pytest/config/__init__.py | 2 +- src/_pytest/main.py | 84 +++++++++++++++++----------------- src/_pytest/python.py | 61 ++++++++++++------------ testing/test_collection.py | 4 +- 5 files changed, 77 insertions(+), 75 deletions(-) diff --git a/changelog/8174.trivial.rst b/changelog/8174.trivial.rst index 001ae4cb193..7649764618f 100644 --- a/changelog/8174.trivial.rst +++ b/changelog/8174.trivial.rst @@ -3,3 +3,4 @@ The following changes have been made to internal pytest types/functions: - The ``path`` property of ``_pytest.code.Code`` returns ``Path`` instead of ``py.path.local``. - The ``path`` property of ``_pytest.code.TracebackEntry`` returns ``Path`` instead of ``py.path.local``. - The ``_pytest.code.getfslineno()`` function returns ``Path`` instead of ``py.path.local``. +- The ``_pytest.python.path_matches_patterns()`` function takes ``Path`` instead of ``py.path.local``. diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index c9a0e78bfcf..760b0f55c7b 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -348,7 +348,7 @@ def __init__(self) -> None: self._conftestpath2mod: Dict[Path, types.ModuleType] = {} self._confcutdir: Optional[Path] = None self._noconftest = False - self._duplicatepaths: Set[py.path.local] = set() + self._duplicatepaths: Set[Path] = set() # plugins that were explicitly skipped with pytest.skip # list of (module name, skip reason) diff --git a/src/_pytest/main.py b/src/_pytest/main.py index e7c31ecc1d5..79afdde6155 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -37,6 +37,7 @@ from _pytest.outcomes import exit from _pytest.pathlib import absolutepath from _pytest.pathlib import bestrelpath +from _pytest.pathlib import fnmatch_ex from _pytest.pathlib import visit from _pytest.reports import CollectReport from _pytest.reports import TestReport @@ -353,11 +354,14 @@ def pytest_runtestloop(session: "Session") -> bool: return True -def _in_venv(path: py.path.local) -> bool: +def _in_venv(path: Path) -> bool: """Attempt to detect if ``path`` is the root of a Virtual Environment by checking for the existence of the appropriate activate script.""" - bindir = path.join("Scripts" if sys.platform.startswith("win") else "bin") - if not bindir.isdir(): + bindir = path.joinpath("Scripts" if sys.platform.startswith("win") else "bin") + try: + if not bindir.is_dir(): + return False + except OSError: return False activates = ( "activate", @@ -367,33 +371,32 @@ def _in_venv(path: py.path.local) -> bool: "Activate.bat", "Activate.ps1", ) - return any([fname.basename in activates for fname in bindir.listdir()]) + return any(fname.name in activates for fname in bindir.iterdir()) -def pytest_ignore_collect(path: py.path.local, config: Config) -> Optional[bool]: - path_ = Path(path) - ignore_paths = config._getconftest_pathlist("collect_ignore", path=path_.parent) +def pytest_ignore_collect(fspath: Path, config: Config) -> Optional[bool]: + ignore_paths = config._getconftest_pathlist("collect_ignore", path=fspath.parent) ignore_paths = ignore_paths or [] excludeopt = config.getoption("ignore") if excludeopt: ignore_paths.extend(absolutepath(x) for x in excludeopt) - if path_ in ignore_paths: + if fspath in ignore_paths: return True ignore_globs = config._getconftest_pathlist( - "collect_ignore_glob", path=path_.parent + "collect_ignore_glob", path=fspath.parent ) ignore_globs = ignore_globs or [] excludeglobopt = config.getoption("ignore_glob") if excludeglobopt: ignore_globs.extend(absolutepath(x) for x in excludeglobopt) - if any(fnmatch.fnmatch(str(path), str(glob)) for glob in ignore_globs): + if any(fnmatch.fnmatch(str(fspath), str(glob)) for glob in ignore_globs): return True allow_in_venv = config.getoption("collect_in_virtualenv") - if not allow_in_venv and _in_venv(path): + if not allow_in_venv and _in_venv(fspath): return True return None @@ -538,21 +541,21 @@ def _recurse(self, direntry: "os.DirEntry[str]") -> bool: if ihook.pytest_ignore_collect(fspath=fspath, path=path, config=self.config): return False norecursepatterns = self.config.getini("norecursedirs") - if any(path.check(fnmatch=pat) for pat in norecursepatterns): + if any(fnmatch_ex(pat, fspath) for pat in norecursepatterns): return False return True def _collectfile( - self, path: py.path.local, handle_dupes: bool = True + self, fspath: Path, handle_dupes: bool = True ) -> Sequence[nodes.Collector]: - fspath = Path(path) + path = py.path.local(fspath) assert ( - path.isfile() + fspath.is_file() ), "{!r} is not a file (isdir={!r}, exists={!r}, islink={!r})".format( - path, path.isdir(), path.exists(), path.islink() + fspath, fspath.is_dir(), fspath.exists(), fspath.is_symlink() ) - ihook = self.gethookproxy(path) - if not self.isinitpath(path): + ihook = self.gethookproxy(fspath) + if not self.isinitpath(fspath): if ihook.pytest_ignore_collect( fspath=fspath, path=path, config=self.config ): @@ -562,10 +565,10 @@ def _collectfile( keepduplicates = self.config.getoption("keepduplicates") if not keepduplicates: duplicate_paths = self.config.pluginmanager._duplicatepaths - if path in duplicate_paths: + if fspath in duplicate_paths: return () else: - duplicate_paths.add(path) + duplicate_paths.add(fspath) return ihook.pytest_collect_file(fspath=fspath, path=path, parent=self) # type: ignore[no-any-return] @@ -652,10 +655,8 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: from _pytest.python import Package # Keep track of any collected nodes in here, so we don't duplicate fixtures. - node_cache1: Dict[py.path.local, Sequence[nodes.Collector]] = {} - node_cache2: Dict[ - Tuple[Type[nodes.Collector], py.path.local], nodes.Collector - ] = ({}) + node_cache1: Dict[Path, Sequence[nodes.Collector]] = {} + node_cache2: Dict[Tuple[Type[nodes.Collector], Path], nodes.Collector] = ({}) # Keep track of any collected collectors in matchnodes paths, so they # are not collected more than once. @@ -679,31 +680,31 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: break if parent.is_dir(): - pkginit = py.path.local(parent / "__init__.py") - if pkginit.isfile() and pkginit not in node_cache1: + pkginit = parent / "__init__.py" + if pkginit.is_file() and pkginit not in node_cache1: col = self._collectfile(pkginit, handle_dupes=False) if col: if isinstance(col[0], Package): pkg_roots[str(parent)] = col[0] - node_cache1[col[0].fspath] = [col[0]] + node_cache1[Path(col[0].fspath)] = [col[0]] # If it's a directory argument, recurse and look for any Subpackages. # Let the Package collector deal with subnodes, don't collect here. if argpath.is_dir(): assert not names, "invalid arg {!r}".format((argpath, names)) - seen_dirs: Set[py.path.local] = set() + seen_dirs: Set[Path] = set() for direntry in visit(str(argpath), self._recurse): if not direntry.is_file(): continue - path = py.path.local(direntry.path) - dirpath = path.dirpath() + path = Path(direntry.path) + dirpath = path.parent if dirpath not in seen_dirs: # Collect packages first. seen_dirs.add(dirpath) - pkginit = dirpath.join("__init__.py") + pkginit = dirpath / "__init__.py" if pkginit.exists(): for x in self._collectfile(pkginit): yield x @@ -714,23 +715,22 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: continue for x in self._collectfile(path): - key = (type(x), x.fspath) - if key in node_cache2: - yield node_cache2[key] + key2 = (type(x), Path(x.fspath)) + if key2 in node_cache2: + yield node_cache2[key2] else: - node_cache2[key] = x + node_cache2[key2] = x yield x else: assert argpath.is_file() - argpath_ = py.path.local(argpath) - if argpath_ in node_cache1: - col = node_cache1[argpath_] + if argpath in node_cache1: + col = node_cache1[argpath] else: - collect_root = pkg_roots.get(argpath_.dirname, self) - col = collect_root._collectfile(argpath_, handle_dupes=False) + collect_root = pkg_roots.get(str(argpath.parent), self) + col = collect_root._collectfile(argpath, handle_dupes=False) if col: - node_cache1[argpath_] = col + node_cache1[argpath] = col matching = [] work: List[ @@ -846,7 +846,7 @@ def resolve_collection_argument( This function ensures the path exists, and returns a tuple: - (py.path.path("/full/path/to/pkg/tests/test_foo.py"), ["TestClass", "test_foo"]) + (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: diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 27bbb24fe2c..b4605092000 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -66,6 +66,8 @@ from _pytest.mark.structures import normalize_mark_list from _pytest.outcomes import fail from _pytest.outcomes import skip +from _pytest.pathlib import bestrelpath +from _pytest.pathlib import fnmatch_ex from _pytest.pathlib import import_path from _pytest.pathlib import ImportPathMismatchError from _pytest.pathlib import parts @@ -190,11 +192,10 @@ def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]: def pytest_collect_file( fspath: Path, path: py.path.local, parent: nodes.Collector ) -> Optional["Module"]: - ext = path.ext - if ext == ".py": + if fspath.suffix == ".py": if not parent.session.isinitpath(fspath): if not path_matches_patterns( - path, parent.config.getini("python_files") + ["__init__.py"] + fspath, parent.config.getini("python_files") + ["__init__.py"] ): return None ihook = parent.session.gethookproxy(fspath) @@ -205,13 +206,13 @@ def pytest_collect_file( return None -def path_matches_patterns(path: py.path.local, patterns: Iterable[str]) -> bool: +def path_matches_patterns(path: Path, patterns: Iterable[str]) -> bool: """Return whether path matches any of the patterns in the list of globs given.""" - return any(path.fnmatch(pattern) for pattern in patterns) + return any(fnmatch_ex(pattern, path) for pattern in patterns) -def pytest_pycollect_makemodule(path: py.path.local, parent) -> "Module": - if path.basename == "__init__.py": +def pytest_pycollect_makemodule(fspath: Path, path: py.path.local, parent) -> "Module": + if fspath.name == "__init__.py": pkg: Package = Package.from_parent(parent, fspath=path) return pkg mod: Module = Module.from_parent(parent, fspath=path) @@ -677,21 +678,21 @@ def _recurse(self, direntry: "os.DirEntry[str]") -> bool: if ihook.pytest_ignore_collect(fspath=fspath, path=path, config=self.config): return False norecursepatterns = self.config.getini("norecursedirs") - if any(path.check(fnmatch=pat) for pat in norecursepatterns): + if any(fnmatch_ex(pat, fspath) for pat in norecursepatterns): return False return True def _collectfile( - self, path: py.path.local, handle_dupes: bool = True + self, fspath: Path, handle_dupes: bool = True ) -> Sequence[nodes.Collector]: - fspath = Path(path) + path = py.path.local(fspath) assert ( - path.isfile() + fspath.is_file() ), "{!r} is not a file (isdir={!r}, exists={!r}, islink={!r})".format( - path, path.isdir(), path.exists(), path.islink() + path, fspath.is_dir(), fspath.exists(), fspath.is_symlink() ) - ihook = self.session.gethookproxy(path) - if not self.session.isinitpath(path): + ihook = self.session.gethookproxy(fspath) + if not self.session.isinitpath(fspath): if ihook.pytest_ignore_collect( fspath=fspath, path=path, config=self.config ): @@ -701,32 +702,32 @@ def _collectfile( keepduplicates = self.config.getoption("keepduplicates") if not keepduplicates: duplicate_paths = self.config.pluginmanager._duplicatepaths - if path in duplicate_paths: + if fspath in duplicate_paths: return () else: - duplicate_paths.add(path) + duplicate_paths.add(fspath) return ihook.pytest_collect_file(fspath=fspath, path=path, parent=self) # type: ignore[no-any-return] def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: - this_path = self.fspath.dirpath() - init_module = this_path.join("__init__.py") - if init_module.check(file=1) and path_matches_patterns( + this_path = Path(self.fspath).parent + init_module = this_path / "__init__.py" + if init_module.is_file() and path_matches_patterns( init_module, self.config.getini("python_files") ): - yield Module.from_parent(self, fspath=init_module) - pkg_prefixes: Set[py.path.local] = set() + yield Module.from_parent(self, fspath=py.path.local(init_module)) + pkg_prefixes: Set[Path] = set() for direntry in visit(str(this_path), recurse=self._recurse): - path = py.path.local(direntry.path) + path = Path(direntry.path) # We will visit our own __init__.py file, in which case we skip it. if direntry.is_file(): - if direntry.name == "__init__.py" and path.dirpath() == this_path: + if direntry.name == "__init__.py" and path.parent == this_path: continue parts_ = parts(direntry.path) if any( - str(pkg_prefix) in parts_ and pkg_prefix.join("__init__.py") != path + str(pkg_prefix) in parts_ and pkg_prefix / "__init__.py" != path for pkg_prefix in pkg_prefixes ): continue @@ -736,7 +737,7 @@ def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: elif not direntry.is_dir(): # Broken symlink or invalid/missing file. continue - elif path.join("__init__.py").check(file=1): + elif path.joinpath("__init__.py").is_file(): pkg_prefixes.add(path) @@ -1416,13 +1417,13 @@ def _show_fixtures_per_test(config: Config, session: Session) -> None: import _pytest.config session.perform_collect() - curdir = py.path.local() + curdir = Path.cwd() tw = _pytest.config.create_terminal_writer(config) verbose = config.getvalue("verbose") - def get_best_relpath(func): + def get_best_relpath(func) -> str: loc = getlocation(func, str(curdir)) - return curdir.bestrelpath(py.path.local(loc)) + return bestrelpath(curdir, Path(loc)) def write_fixture(fixture_def: fixtures.FixtureDef[object]) -> None: argname = fixture_def.argname @@ -1472,7 +1473,7 @@ def _showfixtures_main(config: Config, session: Session) -> None: import _pytest.config session.perform_collect() - curdir = py.path.local() + curdir = Path.cwd() tw = _pytest.config.create_terminal_writer(config) verbose = config.getvalue("verbose") @@ -1494,7 +1495,7 @@ def _showfixtures_main(config: Config, session: Session) -> None: ( len(fixturedef.baseid), fixturedef.func.__module__, - curdir.bestrelpath(py.path.local(loc)), + bestrelpath(curdir, Path(loc)), fixturedef.argname, fixturedef, ) diff --git a/testing/test_collection.py b/testing/test_collection.py index 9733b4fbd47..3dd9283eced 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -212,12 +212,12 @@ def test__in_venv(self, pytester: Pytester, fname: str) -> None: bindir = "Scripts" if sys.platform.startswith("win") else "bin" # no bin/activate, not a virtualenv base_path = pytester.mkdir("venv") - assert _in_venv(py.path.local(base_path)) is False + assert _in_venv(base_path) is False # with bin/activate, totally a virtualenv bin_path = base_path.joinpath(bindir) bin_path.mkdir() bin_path.joinpath(fname).touch() - assert _in_venv(py.path.local(base_path)) is True + assert _in_venv(base_path) is True def test_custom_norecursedirs(self, pytester: Pytester) -> None: pytester.makeini( From 5e323becb7df4ecafc7fba16b31a5a8f83d5e28d Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 22 Dec 2020 21:13:34 +0200 Subject: [PATCH 0040/2772] Revert "doc: temporary workaround for pytest-pygments lexing error" Support was added in pytest-pygments 2.2.0. This reverts commit 0feeddf8edb87052402fafe690d019e3eb75dfa4. --- doc/en/fixture.rst | 2 +- doc/en/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index c74984563ab..752385adc89 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -1975,7 +1975,7 @@ Example: Running this test will *skip* the invocation of ``data_set`` with value ``2``: -.. code-block:: +.. code-block:: pytest $ pytest test_fixture_marks.py -v =========================== test session starts ============================ diff --git a/doc/en/requirements.txt b/doc/en/requirements.txt index fa37acfb447..20246acb750 100644 --- a/doc/en/requirements.txt +++ b/doc/en/requirements.txt @@ -1,5 +1,5 @@ pallets-sphinx-themes -pygments-pytest>=1.1.0 +pygments-pytest>=2.2.0 sphinx-removed-in>=0.2.0 sphinx>=3.1,<4 sphinxcontrib-trio From 35d6a7e78e016b0bbdbbbb7674cbe2207155ca49 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 22 Dec 2020 14:57:04 -0800 Subject: [PATCH 0041/2772] Add badge for pre-commit.ci See #8186 --- README.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.rst b/README.rst index 46b07e59d15..778faf89e50 100644 --- a/README.rst +++ b/README.rst @@ -23,6 +23,10 @@ .. image:: https://github.com/pytest-dev/pytest/workflows/main/badge.svg :target: https://github.com/pytest-dev/pytest/actions?query=workflow%3Amain +.. image:: https://results.pre-commit.ci/badge/github/pytest-dev/pytest/master.svg + :target: https://results.pre-commit.ci/latest/github/pytest-dev/pytest/master + :alt: pre-commit.ci status + .. image:: https://img.shields.io/badge/code%20style-black-000000.svg :target: https://github.com/psf/black From 6d3a66d947a57fed99dcb4bae47062cd9ce6a5f2 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 26 Dec 2020 19:54:07 +0200 Subject: [PATCH 0042/2772] nodes: avoid needing to expose NodeKeywords for typing It adds no value over exporting just the ABC so do that to reduce the API surface. --- src/_pytest/nodes.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index da2a0a7ea79..c6eb49dec4a 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -1,10 +1,12 @@ import os import warnings from pathlib import Path +from typing import Any from typing import Callable from typing import Iterable from typing import Iterator from typing import List +from typing import MutableMapping from typing import Optional from typing import overload from typing import Set @@ -148,8 +150,9 @@ def __init__( #: Filesystem path where this node was collected from (can be None). self.fspath = fspath or getattr(parent, "fspath", None) + # The explicit annotation is to avoid publicly exposing NodeKeywords. #: Keywords/markers collected from all scopes. - self.keywords = NodeKeywords(self) + self.keywords: MutableMapping[str, Any] = NodeKeywords(self) #: The marker objects belonging to this node. self.own_markers: List[Mark] = [] From bd76042344b3c3318dddf991c08d49bbce2251bb Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 26 Dec 2020 20:49:17 +0200 Subject: [PATCH 0043/2772] python: export pytest.Metafunc for typing purposes The type cannot be constructed directly, but is exported for use in type annotations, since it is reachable through existing public API. --- changelog/7469.deprecation.rst | 1 + changelog/7469.feature.rst | 1 + doc/en/deprecations.rst | 4 ++-- doc/en/funcarg_compare.rst | 4 ++-- doc/en/reference.rst | 4 ++-- src/_pytest/python.py | 12 +++++++++++- src/pytest/__init__.py | 2 ++ testing/python/metafunc.py | 2 +- 8 files changed, 22 insertions(+), 8 deletions(-) diff --git a/changelog/7469.deprecation.rst b/changelog/7469.deprecation.rst index 6bbc8075522..bcf4266d8bd 100644 --- a/changelog/7469.deprecation.rst +++ b/changelog/7469.deprecation.rst @@ -3,5 +3,6 @@ Directly constructing the following classes is now deprecated: - ``_pytest.mark.structures.Mark`` - ``_pytest.mark.structures.MarkDecorator`` - ``_pytest.mark.structures.MarkGenerator`` +- ``_pytest.python.Metafunc`` These have always been considered private, but now issue a deprecation warning, which may become a hard error in pytest 7.0.0. diff --git a/changelog/7469.feature.rst b/changelog/7469.feature.rst index 81f93d1f7af..0ab2b48c42e 100644 --- a/changelog/7469.feature.rst +++ b/changelog/7469.feature.rst @@ -5,6 +5,7 @@ The newly-exported types are: - ``pytest.Mark`` for :class:`marks `. - ``pytest.MarkDecorator`` for :class:`mark decorators `. - ``pytest.MarkGenerator`` for the :class:`pytest.mark ` singleton. +- ``pytest.Metafunc`` for the :class:`metafunc ` argument to the `pytest_generate_tests ` hook. Constructing them directly is not supported; they are only meant for use in type annotations. Doing so will emit a deprecation warning, and may become a hard-error in pytest 7.0. diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index 5ef1053e0b4..ec2397e596f 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -397,8 +397,8 @@ Metafunc.addcall .. versionremoved:: 4.0 -``_pytest.python.Metafunc.addcall`` was a precursor to the current parametrized mechanism. Users should use -:meth:`_pytest.python.Metafunc.parametrize` instead. +``Metafunc.addcall`` was a precursor to the current parametrized mechanism. Users should use +:meth:`pytest.Metafunc.parametrize` instead. Example: diff --git a/doc/en/funcarg_compare.rst b/doc/en/funcarg_compare.rst index 0c4913edff8..5e2a050063c 100644 --- a/doc/en/funcarg_compare.rst +++ b/doc/en/funcarg_compare.rst @@ -47,7 +47,7 @@ There are several limitations and difficulties with this approach: 2. parametrizing the "db" resource is not straight forward: you need to apply a "parametrize" decorator or implement a :py:func:`~hookspec.pytest_generate_tests` hook - calling :py:func:`~python.Metafunc.parametrize` which + calling :py:func:`~pytest.Metafunc.parametrize` which performs parametrization at the places where the resource is used. Moreover, you need to modify the factory to use an ``extrakey`` parameter containing ``request.param`` to the @@ -113,7 +113,7 @@ This new way of parametrizing funcarg factories should in many cases allow to re-use already written factories because effectively ``request.param`` was already used when test functions/classes were parametrized via -:py:func:`metafunc.parametrize(indirect=True) <_pytest.python.Metafunc.parametrize>` calls. +:py:func:`metafunc.parametrize(indirect=True) ` calls. Of course it's perfectly fine to combine parametrization and scoping: diff --git a/doc/en/reference.rst b/doc/en/reference.rst index c8e8dca7561..7f2ae01058f 100644 --- a/doc/en/reference.rst +++ b/doc/en/reference.rst @@ -138,7 +138,7 @@ pytest.mark.parametrize **Tutorial**: :doc:`parametrize`. -This mark has the same signature as :py:meth:`_pytest.python.Metafunc.parametrize`; see there. +This mark has the same signature as :py:meth:`pytest.Metafunc.parametrize`; see there. .. _`pytest.mark.skip ref`: @@ -870,7 +870,7 @@ Mark Metafunc ~~~~~~~~ -.. autoclass:: _pytest.python.Metafunc +.. autoclass:: pytest.Metafunc() :members: Module diff --git a/src/_pytest/python.py b/src/_pytest/python.py index b4605092000..31d91853f2f 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -55,6 +55,7 @@ from _pytest.config import ExitCode from _pytest.config import hookimpl from _pytest.config.argparsing import Parser +from _pytest.deprecated import check_ispytest from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH from _pytest.fixtures import FuncFixtureInfo from _pytest.main import Session @@ -467,7 +468,12 @@ def _genfunctions(self, name: str, funcobj) -> Iterator["Function"]: fixtureinfo = definition._fixtureinfo metafunc = Metafunc( - definition, fixtureinfo, self.config, cls=cls, module=module + definition=definition, + fixtureinfo=fixtureinfo, + config=self.config, + cls=cls, + module=module, + _ispytest=True, ) methods = [] if hasattr(module, "pytest_generate_tests"): @@ -971,7 +977,11 @@ def __init__( config: Config, cls=None, module=None, + *, + _ispytest: bool = False, ) -> None: + check_ispytest(_ispytest) + #: Access to the underlying :class:`_pytest.python.FunctionDefinition`. self.definition = definition diff --git a/src/pytest/__init__.py b/src/pytest/__init__.py index 74cf00ee2c4..f97b0ac2e8c 100644 --- a/src/pytest/__init__.py +++ b/src/pytest/__init__.py @@ -40,6 +40,7 @@ from _pytest.python import Class from _pytest.python import Function from _pytest.python import Instance +from _pytest.python import Metafunc from _pytest.python import Module from _pytest.python import Package from _pytest.python_api import approx @@ -95,6 +96,7 @@ "Mark", "MarkDecorator", "MarkGenerator", + "Metafunc", "Module", "MonkeyPatch", "Package", diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index c50ea53d255..58a902a3a59 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -47,7 +47,7 @@ class DefinitionMock(python.FunctionDefinition): names = getfuncargnames(func) fixtureinfo: Any = FuncFixtureInfoMock(names) definition: Any = DefinitionMock._create(func, "mock::nodeid") - return python.Metafunc(definition, fixtureinfo, config) + return python.Metafunc(definition, fixtureinfo, config, _ispytest=True) def test_no_funcargs(self) -> None: def function(): From 96ea867fec556a8d0e2b60392927572da38c88df Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 26 Dec 2020 21:23:23 +0200 Subject: [PATCH 0044/2772] runner: export pytest.CallInfo for typing purposes The type cannot be constructed directly, but is exported for use in type annotations, since it is reachable through existing public API. This also documents `from_call` as public, because at least pytest-forked uses it, so we must treat it as public already anyway. --- changelog/7469.deprecation.rst | 1 + changelog/7469.feature.rst | 1 + doc/en/reference.rst | 2 +- src/_pytest/runner.py | 73 +++++++++++++++++++++++----------- src/pytest/__init__.py | 2 + 5 files changed, 54 insertions(+), 25 deletions(-) diff --git a/changelog/7469.deprecation.rst b/changelog/7469.deprecation.rst index bcf4266d8bd..0d7908ef8ac 100644 --- a/changelog/7469.deprecation.rst +++ b/changelog/7469.deprecation.rst @@ -4,5 +4,6 @@ Directly constructing the following classes is now deprecated: - ``_pytest.mark.structures.MarkDecorator`` - ``_pytest.mark.structures.MarkGenerator`` - ``_pytest.python.Metafunc`` +- ``_pytest.runner.CallInfo`` These have always been considered private, but now issue a deprecation warning, which may become a hard error in pytest 7.0.0. diff --git a/changelog/7469.feature.rst b/changelog/7469.feature.rst index 0ab2b48c42e..f9948d686f9 100644 --- a/changelog/7469.feature.rst +++ b/changelog/7469.feature.rst @@ -6,6 +6,7 @@ The newly-exported types are: - ``pytest.MarkDecorator`` for :class:`mark decorators `. - ``pytest.MarkGenerator`` for the :class:`pytest.mark ` singleton. - ``pytest.Metafunc`` for the :class:`metafunc ` argument to the `pytest_generate_tests ` hook. +- ``pytest.runner.CallInfo`` for the :class:`CallInfo ` type passed to various hooks. Constructing them directly is not supported; they are only meant for use in type annotations. Doing so will emit a deprecation warning, and may become a hard-error in pytest 7.0. diff --git a/doc/en/reference.rst b/doc/en/reference.rst index 7f2ae01058f..bc6c5670a5c 100644 --- a/doc/en/reference.rst +++ b/doc/en/reference.rst @@ -758,7 +758,7 @@ Full reference to objects accessible from :ref:`fixtures ` or :ref:`hoo CallInfo ~~~~~~~~ -.. autoclass:: _pytest.runner.CallInfo() +.. autoclass:: pytest.CallInfo() :members: diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 794690ddb0b..df046a78aca 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -26,6 +26,7 @@ from _pytest._code.code import TerminalRepr from _pytest.compat import final from _pytest.config.argparsing import Parser +from _pytest.deprecated import check_ispytest from _pytest.nodes import Collector from _pytest.nodes import Item from _pytest.nodes import Node @@ -260,34 +261,47 @@ def call_runtest_hook( @final -@attr.s(repr=False) +@attr.s(repr=False, init=False, auto_attribs=True) class CallInfo(Generic[TResult]): - """Result/Exception info a function invocation. - - :param T result: - The return value of the call, if it didn't raise. Can only be - accessed if excinfo is None. - :param Optional[ExceptionInfo] excinfo: - The captured exception of the call, if it raised. - :param float start: - The system time when the call started, in seconds since the epoch. - :param float stop: - The system time when the call ended, in seconds since the epoch. - :param float duration: - The call duration, in seconds. - :param str when: - The context of invocation: "setup", "call", "teardown", ... - """ - - _result = attr.ib(type="Optional[TResult]") - excinfo = attr.ib(type=Optional[ExceptionInfo[BaseException]]) - start = attr.ib(type=float) - stop = attr.ib(type=float) - duration = attr.ib(type=float) - when = attr.ib(type="Literal['collect', 'setup', 'call', 'teardown']") + """Result/Exception info of a function invocation.""" + + _result: Optional[TResult] + #: The captured exception of the call, if it raised. + excinfo: Optional[ExceptionInfo[BaseException]] + #: The system time when the call started, in seconds since the epoch. + start: float + #: The system time when the call ended, in seconds since the epoch. + stop: float + #: The call duration, in seconds. + duration: float + #: The context of invocation: "collect", "setup", "call" or "teardown". + when: "Literal['collect', 'setup', 'call', 'teardown']" + + def __init__( + self, + result: Optional[TResult], + excinfo: Optional[ExceptionInfo[BaseException]], + start: float, + stop: float, + duration: float, + when: "Literal['collect', 'setup', 'call', 'teardown']", + *, + _ispytest: bool = False, + ) -> None: + check_ispytest(_ispytest) + self._result = result + self.excinfo = excinfo + self.start = start + self.stop = stop + self.duration = duration + self.when = when @property def result(self) -> TResult: + """The return value of the call, if it didn't raise. + + Can only be accessed if excinfo is None. + """ if self.excinfo is not None: raise AttributeError(f"{self!r} has no valid result") # The cast is safe because an exception wasn't raised, hence @@ -304,6 +318,16 @@ def from_call( Union[Type[BaseException], Tuple[Type[BaseException], ...]] ] = None, ) -> "CallInfo[TResult]": + """Call func, wrapping the result in a CallInfo. + + :param func: + The function to call. Called without arguments. + :param when: + The phase in which the function is called. + :param reraise: + Exception or exceptions that shall propagate if raised by the + function, instead of being wrapped in the CallInfo. + """ excinfo = None start = timing.time() precise_start = timing.perf_counter() @@ -325,6 +349,7 @@ def from_call( when=when, result=result, excinfo=excinfo, + _ispytest=True, ) def __repr__(self) -> str: diff --git a/src/pytest/__init__.py b/src/pytest/__init__.py index f97b0ac2e8c..53917340fd7 100644 --- a/src/pytest/__init__.py +++ b/src/pytest/__init__.py @@ -48,6 +48,7 @@ from _pytest.recwarn import deprecated_call from _pytest.recwarn import WarningsRecorder from _pytest.recwarn import warns +from _pytest.runner import CallInfo from _pytest.tmpdir import TempdirFactory from _pytest.tmpdir import TempPathFactory from _pytest.warning_types import PytestAssertRewriteWarning @@ -69,6 +70,7 @@ "_fillfuncargs", "approx", "Cache", + "CallInfo", "CaptureFixture", "Class", "cmdline", From 8550c29180afb6f1138c81645f7d92f644aaf74d Mon Sep 17 00:00:00 2001 From: antonblr Date: Tue, 22 Dec 2020 20:27:00 -0800 Subject: [PATCH 0045/2772] coverage: Include code that runs in subprocesses --- .github/workflows/main.yml | 17 +++------ scripts/append_codecov_token.py | 36 ------------------- ...{report-coverage.sh => upload-coverage.sh} | 2 -- 3 files changed, 4 insertions(+), 51 deletions(-) delete mode 100644 scripts/append_codecov_token.py rename scripts/{report-coverage.sh => upload-coverage.sh} (88%) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index beb50178528..a3ea24b7cb0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -137,22 +137,13 @@ jobs: - name: Test with coverage if: "matrix.use_coverage" - env: - _PYTEST_TOX_COVERAGE_RUN: "coverage run -m" - COVERAGE_PROCESS_START: ".coveragerc" - _PYTEST_TOX_EXTRA_DEP: "coverage-enable-subprocess" - run: "tox -e ${{ matrix.tox_env }}" - - - name: Prepare coverage token - if: (matrix.use_coverage && ( github.repository == 'pytest-dev/pytest' || github.event_name == 'pull_request' )) - run: | - python scripts/append_codecov_token.py + run: "tox -e ${{ matrix.tox_env }}-coverage" - - name: Report coverage - if: (matrix.use_coverage) + - name: Upload coverage + if: matrix.use_coverage && github.repository == 'pytest-dev/pytest' env: CODECOV_NAME: ${{ matrix.name }} - run: bash scripts/report-coverage.sh -F GHA,${{ runner.os }} + run: bash scripts/upload-coverage.sh -F GHA,${{ runner.os }} linting: runs-on: ubuntu-latest diff --git a/scripts/append_codecov_token.py b/scripts/append_codecov_token.py deleted file mode 100644 index 5c617aafb54..00000000000 --- a/scripts/append_codecov_token.py +++ /dev/null @@ -1,36 +0,0 @@ -""" -Appends the codecov token to the 'codecov.yml' file at the root of the repository. - -This is done by CI during PRs and builds on the pytest-dev repository so we can -upload coverage, at least until codecov grows some native integration with GitHub Actions. - -See discussion in https://github.com/pytest-dev/pytest/pull/6441 for more information. -""" -import os.path -from textwrap import dedent - - -def main(): - this_dir = os.path.dirname(__file__) - cov_file = os.path.join(this_dir, "..", "codecov.yml") - - assert os.path.isfile(cov_file), "{cov_file} does not exist".format( - cov_file=cov_file - ) - - with open(cov_file, "a") as f: - # token from: https://codecov.io/gh/pytest-dev/pytest/settings - # use same URL to regenerate it if needed - text = dedent( - """ - codecov: - token: "1eca3b1f-31a2-4fb8-a8c3-138b441b50a7" - """ - ) - f.write(text) - - print("Token updated:", cov_file) - - -if __name__ == "__main__": - main() diff --git a/scripts/report-coverage.sh b/scripts/upload-coverage.sh similarity index 88% rename from scripts/report-coverage.sh rename to scripts/upload-coverage.sh index fbcf20ca929..ad3dd482814 100755 --- a/scripts/report-coverage.sh +++ b/scripts/upload-coverage.sh @@ -10,9 +10,7 @@ else PATH="$PWD/.tox/${TOXENV##*,}/bin:$PATH" fi -python -m coverage combine python -m coverage xml -python -m coverage report -m # Set --connect-timeout to work around https://github.com/curl/curl/issues/4461 curl -S -L --connect-timeout 5 --retry 6 -s https://codecov.io/bash -o codecov-upload.sh bash codecov-upload.sh -Z -X fix -f coverage.xml "$@" From bf9b59b3c8571b26f65159ed3fec831eae434561 Mon Sep 17 00:00:00 2001 From: Christophe Bedard Date: Sun, 27 Dec 2020 09:55:21 -0500 Subject: [PATCH 0046/2772] Add missing space in '--version' help message --- src/_pytest/helpconfig.py | 2 +- testing/test_helpconfig.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/_pytest/helpconfig.py b/src/_pytest/helpconfig.py index 4384d07b261..b9360cecf67 100644 --- a/src/_pytest/helpconfig.py +++ b/src/_pytest/helpconfig.py @@ -51,7 +51,7 @@ def pytest_addoption(parser: Parser) -> None: action="count", default=0, dest="version", - help="display pytest version and information about plugins." + help="display pytest version and information about plugins. " "When given twice, also display information about plugins.", ) group._addoption( diff --git a/testing/test_helpconfig.py b/testing/test_helpconfig.py index 9a433b1b17e..571a4783e67 100644 --- a/testing/test_helpconfig.py +++ b/testing/test_helpconfig.py @@ -28,6 +28,9 @@ def test_help(pytester: Pytester) -> None: For example: -m 'mark1 and not mark2'. reporting: --durations=N * + -V, --version display pytest version and information about plugins. + When given twice, also display information about + plugins. *setup.cfg* *minversion* *to see*markers*pytest --markers* From ee03e31831900c3a7aba9f94a9693a833a3ab9de Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 30 Dec 2020 11:56:09 +0200 Subject: [PATCH 0047/2772] [pre-commit.ci] pre-commit autoupdate (#8201) * [pre-commit.ci] pre-commit autoupdate * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * manual fixes after configuration update * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Anthony Sottile --- .pre-commit-config.yaml | 20 +++++----- doc/en/doctest.rst | 2 +- doc/en/writing_plugins.rst | 3 +- doc/en/xunit_setup.rst | 14 +++---- scripts/prepare-release-pr.py | 10 ++++- scripts/release-on-comment.py | 5 ++- src/_pytest/_code/code.py | 6 +-- src/_pytest/_io/saferepr.py | 7 +++- src/_pytest/cacheprovider.py | 3 +- src/_pytest/capture.py | 10 ++++- src/_pytest/compat.py | 3 +- src/_pytest/config/__init__.py | 39 ++++++++++++++----- src/_pytest/config/findpaths.py | 4 +- src/_pytest/doctest.py | 19 ++++++--- src/_pytest/fixtures.py | 21 +++++++--- src/_pytest/freeze_support.py | 3 +- src/_pytest/hookspec.py | 25 +++++++----- src/_pytest/junitxml.py | 2 +- src/_pytest/logging.py | 6 ++- src/_pytest/main.py | 8 ++-- src/_pytest/mark/__init__.py | 5 ++- src/_pytest/mark/expression.py | 10 +++-- src/_pytest/mark/structures.py | 8 +--- src/_pytest/monkeypatch.py | 14 +++++-- src/_pytest/nodes.py | 5 ++- src/_pytest/pytester.py | 19 ++++++--- src/_pytest/python.py | 11 ++++-- src/_pytest/reports.py | 2 +- src/_pytest/terminal.py | 7 +++- src/_pytest/threadexception.py | 4 +- src/_pytest/tmpdir.py | 5 ++- .../test_compare_recursive_dataclasses.py | 10 ++++- testing/io/test_terminalwriter.py | 15 +++++-- testing/python/approx.py | 3 +- testing/python/metafunc.py | 10 ++++- testing/test_capture.py | 3 +- testing/test_debugging.py | 4 +- testing/test_doctest.py | 4 +- testing/test_mark.py | 13 +++++-- testing/test_mark_expression.py | 18 +++++++-- testing/test_monkeypatch.py | 4 +- testing/test_pluginmanager.py | 4 +- testing/test_runner_xunit.py | 4 +- testing/test_stepwise.py | 7 +++- testing/test_warnings.py | 2 +- 45 files changed, 280 insertions(+), 121 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 68cc3273bba..c8e19b283f8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,16 +1,16 @@ repos: - repo: https://github.com/psf/black - rev: 19.10b0 + rev: 20.8b1 hooks: - id: black args: [--safe, --quiet] - repo: https://github.com/asottile/blacken-docs - rev: v1.8.0 + rev: v1.9.1 hooks: - id: blacken-docs - additional_dependencies: [black==19.10b0] + additional_dependencies: [black==20.8b1] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.2.0 + rev: v3.4.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -21,7 +21,7 @@ repos: exclude: _pytest/(debugging|hookspec).py language_version: python3 - repo: https://gitlab.com/pycqa/flake8 - rev: 3.8.3 + rev: 3.8.4 hooks: - id: flake8 language_version: python3 @@ -29,23 +29,21 @@ repos: - flake8-typing-imports==1.9.0 - flake8-docstrings==1.5.0 - repo: https://github.com/asottile/reorder_python_imports - rev: v2.3.5 + rev: v2.3.6 hooks: - id: reorder-python-imports args: ['--application-directories=.:src', --py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.7.2 + rev: v2.7.4 hooks: - id: pyupgrade args: [--py36-plus] - repo: https://github.com/asottile/setup-cfg-fmt - rev: v1.11.0 + rev: v1.16.0 hooks: - id: setup-cfg-fmt - # TODO: when upgrading setup-cfg-fmt this can be removed - args: [--max-py-version=3.9] - repo: https://github.com/pre-commit/pygrep-hooks - rev: v1.6.0 + rev: v1.7.0 hooks: - id: python-use-type-annotations - repo: https://github.com/pre-commit/mirrors-mypy diff --git a/doc/en/doctest.rst b/doc/en/doctest.rst index f8d010679f0..486868bb806 100644 --- a/doc/en/doctest.rst +++ b/doc/en/doctest.rst @@ -48,7 +48,7 @@ and functions, including from test modules: # content of mymodule.py def something(): - """ a doctest in a docstring + """a doctest in a docstring >>> something() 42 """ diff --git a/doc/en/writing_plugins.rst b/doc/en/writing_plugins.rst index 908366d5290..f53f561cfad 100644 --- a/doc/en/writing_plugins.rst +++ b/doc/en/writing_plugins.rst @@ -762,8 +762,7 @@ declaring the hook functions directly in your plugin module, for example: """Simple plugin to defer pytest-xdist hook functions.""" def pytest_testnodedown(self, node, error): - """standard xdist hook function. - """ + """standard xdist hook function.""" def pytest_configure(config): diff --git a/doc/en/xunit_setup.rst b/doc/en/xunit_setup.rst index 8b3366f62ae..4fea863be63 100644 --- a/doc/en/xunit_setup.rst +++ b/doc/en/xunit_setup.rst @@ -36,7 +36,7 @@ which will usually be called once for all the functions: def teardown_module(module): - """ teardown any state that was previously setup with a setup_module + """teardown any state that was previously setup with a setup_module method. """ @@ -52,14 +52,14 @@ and after all test methods of the class are called: @classmethod def setup_class(cls): - """ setup any state specific to the execution of the given class (which + """setup any state specific to the execution of the given class (which usually contains tests). """ @classmethod def teardown_class(cls): - """ teardown any state that was previously setup with a call to + """teardown any state that was previously setup with a call to setup_class. """ @@ -71,13 +71,13 @@ Similarly, the following methods are called around each method invocation: .. code-block:: python def setup_method(self, method): - """ setup any state tied to the execution of the given method in a + """setup any state tied to the execution of the given method in a class. setup_method is invoked for every test method of a class. """ def teardown_method(self, method): - """ teardown any state that was previously setup with a setup_method + """teardown any state that was previously setup with a setup_method call. """ @@ -89,13 +89,13 @@ you can also use the following functions to implement fixtures: .. code-block:: python def setup_function(function): - """ setup any state tied to the execution of the given function. + """setup any state tied to the execution of the given function. Invoked for every test function in the module. """ def teardown_function(function): - """ teardown any state that was previously setup with a setup_function + """teardown any state that was previously setup with a setup_function call. """ diff --git a/scripts/prepare-release-pr.py b/scripts/prepare-release-pr.py index 538a5af5a41..296de46ea0c 100644 --- a/scripts/prepare-release-pr.py +++ b/scripts/prepare-release-pr.py @@ -90,7 +90,10 @@ def prepare_release_pr(base_branch: str, is_major: bool, token: str) -> None: cmdline = ["tox", "-e", "release", "--", version, "--skip-check-links"] print("Running", " ".join(cmdline)) run( - cmdline, text=True, check=True, capture_output=True, + cmdline, + text=True, + check=True, + capture_output=True, ) oauth_url = f"https://{token}:x-oauth-basic@github.com/{SLUG}.git" @@ -105,7 +108,10 @@ def prepare_release_pr(base_branch: str, is_major: bool, token: str) -> None: body = PR_BODY.format(version=version) repo = login(token) pr = repo.create_pull( - f"Prepare release {version}", base=base_branch, head=release_branch, body=body, + f"Prepare release {version}", + base=base_branch, + head=release_branch, + body=body, ) print(f"Pull request {Fore.CYAN}{pr.url}{Fore.RESET} created.") diff --git a/scripts/release-on-comment.py b/scripts/release-on-comment.py index 44431a4fc3f..f8af9c0fc83 100644 --- a/scripts/release-on-comment.py +++ b/scripts/release-on-comment.py @@ -153,7 +153,10 @@ def trigger_release(payload_path: Path, token: str) -> None: cmdline = ["tox", "-e", "release", "--", version, "--skip-check-links"] print("Running", " ".join(cmdline)) run( - cmdline, text=True, check=True, capture_output=True, + cmdline, + text=True, + check=True, + capture_output=True, ) oauth_url = f"https://{token}:x-oauth-basic@github.com/{SLUG}.git" diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index 043a23a79af..b8521756067 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -272,9 +272,9 @@ def ishidden(self) -> bool: Mostly for internal use. """ - tbh: Union[bool, Callable[[Optional[ExceptionInfo[BaseException]]], bool]] = ( - False - ) + tbh: Union[ + bool, Callable[[Optional[ExceptionInfo[BaseException]]], bool] + ] = False for maybe_ns_dct in (self.frame.f_locals, self.frame.f_globals): # in normal cases, f_locals and f_globals are dictionaries # however via `exec(...)` / `eval(...)` they can be other types diff --git a/src/_pytest/_io/saferepr.py b/src/_pytest/_io/saferepr.py index 5eb1e088905..440b8cbbb54 100644 --- a/src/_pytest/_io/saferepr.py +++ b/src/_pytest/_io/saferepr.py @@ -107,7 +107,12 @@ def _format( if objid in context or p is None: # Type ignored because _format is private. super()._format( # type: ignore[misc] - object, stream, indent, allowance, context, level, + object, + stream, + indent, + allowance, + context, + level, ) return diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py index 03acd03109e..480319c03b4 100755 --- a/src/_pytest/cacheprovider.py +++ b/src/_pytest/cacheprovider.py @@ -219,7 +219,8 @@ def pytest_make_collect_report(self, collector: nodes.Collector): # Sort any lf-paths to the beginning. lf_paths = self.lfplugin._last_failed_paths res.result = sorted( - res.result, key=lambda x: 0 if Path(str(x.fspath)) in lf_paths else 1, + res.result, + key=lambda x: 0 if Path(str(x.fspath)) in lf_paths else 1, ) return diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index 086302658cb..355f42591a7 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -556,7 +556,11 @@ def __init__(self, in_, out, err) -> None: def __repr__(self) -> str: return "".format( - self.out, self.err, self.in_, self._state, self._in_suspended, + self.out, + self.err, + self.in_, + self._state, + self._in_suspended, ) def start_capturing(self) -> None: @@ -843,7 +847,9 @@ def __init__( def _start(self) -> None: if self._capture is None: self._capture = MultiCapture( - in_=None, out=self.captureclass(1), err=self.captureclass(2), + in_=None, + out=self.captureclass(1), + err=self.captureclass(2), ) self._capture.start_capturing() diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index c7f86ea9c0a..0b87c7bbc08 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -143,7 +143,8 @@ def getfuncargnames( parameters = signature(function).parameters except (ValueError, TypeError) as e: fail( - f"Could not determine arguments of {function!r}: {e}", pytrace=False, + f"Could not determine arguments of {function!r}: {e}", + pytrace=False, ) arg_names = tuple( diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 760b0f55c7b..c029c29a3a2 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -104,7 +104,9 @@ class ExitCode(enum.IntEnum): class ConftestImportFailure(Exception): def __init__( - self, path: Path, excinfo: Tuple[Type[Exception], Exception, TracebackType], + self, + path: Path, + excinfo: Tuple[Type[Exception], Exception, TracebackType], ) -> None: super().__init__(path, excinfo) self.path = path @@ -269,7 +271,9 @@ def get_config( config = Config( pluginmanager, invocation_params=Config.InvocationParams( - args=args or (), plugins=plugins, dir=Path.cwd(), + args=args or (), + plugins=plugins, + dir=Path.cwd(), ), ) @@ -364,7 +368,10 @@ def __init__(self) -> None: encoding: str = getattr(err, "encoding", "utf8") try: err = open( - os.dup(err.fileno()), mode=err.mode, buffering=1, encoding=encoding, + os.dup(err.fileno()), + mode=err.mode, + buffering=1, + encoding=encoding, ) except Exception: pass @@ -516,7 +523,9 @@ def _try_load_conftest( @lru_cache(maxsize=128) def _getconftestmodules( - self, path: Path, importmode: Union[str, ImportMode], + self, + path: Path, + importmode: Union[str, ImportMode], ) -> List[types.ModuleType]: if self._noconftest: return [] @@ -541,7 +550,10 @@ def _getconftestmodules( return clist def _rget_with_confmod( - self, name: str, path: Path, importmode: Union[str, ImportMode], + self, + name: str, + path: Path, + importmode: Union[str, ImportMode], ) -> Tuple[types.ModuleType, Any]: modules = self._getconftestmodules(path, importmode) for mod in reversed(modules): @@ -552,7 +564,9 @@ def _rget_with_confmod( raise KeyError(name) def _importconftest( - self, conftestpath: Path, importmode: Union[str, ImportMode], + self, + conftestpath: Path, + importmode: Union[str, ImportMode], ) -> types.ModuleType: # Use a resolved Path object as key to avoid loading the same conftest # twice with build systems that create build directories containing @@ -590,7 +604,9 @@ def _importconftest( return mod def _check_non_top_pytest_plugins( - self, mod: types.ModuleType, conftestpath: Path, + self, + mod: types.ModuleType, + conftestpath: Path, ) -> None: if ( hasattr(mod, "pytest_plugins") @@ -1227,7 +1243,11 @@ def _checkversion(self) -> None: if Version(minver) > Version(pytest.__version__): raise pytest.UsageError( "%s: 'minversion' requires pytest-%s, actual pytest-%s'" - % (self.inipath, minver, pytest.__version__,) + % ( + self.inipath, + minver, + pytest.__version__, + ) ) def _validate_config_options(self) -> None: @@ -1502,7 +1522,8 @@ def _warn_about_missing_assertion(self, mode: str) -> None: "(are you using python -O?)\n" ) self.issue_config_time_warning( - PytestConfigWarning(warning_text), stacklevel=3, + PytestConfigWarning(warning_text), + stacklevel=3, ) def _warn_about_skipped_plugins(self) -> None: diff --git a/src/_pytest/config/findpaths.py b/src/_pytest/config/findpaths.py index 2edf54536ba..05f21ece5d4 100644 --- a/src/_pytest/config/findpaths.py +++ b/src/_pytest/config/findpaths.py @@ -83,9 +83,7 @@ def make_scalar(v: object) -> Union[str, List[str]]: def locate_config( args: Iterable[Path], -) -> Tuple[ - Optional[Path], Optional[Path], Dict[str, Union[str, List[str]]], -]: +) -> Tuple[Optional[Path], Optional[Path], Dict[str, Union[str, List[str]]]]: """Search in the list of arguments for a valid ini-file for pytest, and return a tuple of (rootdir, inifile, cfg-dict).""" config_names = [ diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py index 24f8882579b..255ca80b913 100644 --- a/src/_pytest/doctest.py +++ b/src/_pytest/doctest.py @@ -121,7 +121,9 @@ def pytest_unconfigure() -> None: def pytest_collect_file( - fspath: Path, path: py.path.local, parent: Collector, + fspath: Path, + path: py.path.local, + parent: Collector, ) -> Optional[Union["DoctestModule", "DoctestTextfile"]]: config = parent.config if fspath.suffix == ".py": @@ -193,7 +195,11 @@ def __init__( self.continue_on_failure = continue_on_failure def report_failure( - self, out, test: "doctest.DocTest", example: "doctest.Example", got: str, + self, + out, + test: "doctest.DocTest", + example: "doctest.Example", + got: str, ) -> None: failure = doctest.DocTestFailure(test, example, got) if self.continue_on_failure: @@ -303,13 +309,14 @@ def _disable_output_capturing_for_darwin(self) -> None: # TODO: Type ignored -- breaks Liskov Substitution. def repr_failure( # type: ignore[override] - self, excinfo: ExceptionInfo[BaseException], + self, + excinfo: ExceptionInfo[BaseException], ) -> Union[str, TerminalRepr]: import doctest failures: Optional[ Sequence[Union[doctest.DocTestFailure, doctest.UnexpectedException]] - ] = (None) + ] = None if isinstance( excinfo.value, (doctest.DocTestFailure, doctest.UnexpectedException) ): @@ -510,7 +517,9 @@ def _find_lineno(self, obj, source_lines): obj = getattr(obj, "fget", obj) # Type ignored because this is a private function. return doctest.DocTestFinder._find_lineno( # type: ignore - self, obj, source_lines, + self, + obj, + source_lines, ) def _find( diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index d3ec1296ab0..53f33d3e13d 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -238,7 +238,7 @@ def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]: def get_parametrized_fixture_keys(item: nodes.Item, scopenum: int) -> Iterator[_Key]: """Return list of keys for all parametrized arguments which match - the specified scope. """ + the specified scope.""" assert scopenum < scopenum_function # function try: callspec = item.callspec # type: ignore[attr-defined] @@ -443,7 +443,7 @@ def __init__(self, pyfuncitem, *, _ispytest: bool = False) -> None: fixtureinfo: FuncFixtureInfo = pyfuncitem._fixtureinfo self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy() self._arg2index: Dict[str, int] = {} - self._fixturemanager: FixtureManager = (pyfuncitem.session._fixturemanager) + self._fixturemanager: FixtureManager = pyfuncitem.session._fixturemanager @property def fixturenames(self) -> List[str]: @@ -700,7 +700,10 @@ def _schedule_finalizers( ) def _check_scope( - self, argname: str, invoking_scope: "_Scope", requested_scope: "_Scope", + self, + argname: str, + invoking_scope: "_Scope", + requested_scope: "_Scope", ) -> None: if argname == "request": return @@ -907,7 +910,8 @@ def toterminal(self, tw: TerminalWriter) -> None: ) for line in lines[1:]: tw.line( - f"{FormattedExcinfo.flow_marker} {line.strip()}", red=True, + f"{FormattedExcinfo.flow_marker} {line.strip()}", + red=True, ) tw.line() tw.line("%s:%d" % (os.fspath(self.filename), self.firstlineno + 1)) @@ -1167,7 +1171,8 @@ def _params_converter( def wrap_function_to_error_out_if_called_directly( - function: _FixtureFunction, fixture_marker: "FixtureFunctionMarker", + function: _FixtureFunction, + fixture_marker: "FixtureFunctionMarker", ) -> _FixtureFunction: """Wrap the given fixture function so we can raise an error about it being called directly, instead of used as an argument in a test function.""" @@ -1332,7 +1337,11 @@ def fixture( ``@pytest.fixture(name='')``. """ fixture_marker = FixtureFunctionMarker( - scope=scope, params=params, autouse=autouse, ids=ids, name=name, + scope=scope, + params=params, + autouse=autouse, + ids=ids, + name=name, ) # Direct decoration. diff --git a/src/_pytest/freeze_support.py b/src/_pytest/freeze_support.py index 8b93ed5f7f8..69b7d59ff69 100644 --- a/src/_pytest/freeze_support.py +++ b/src/_pytest/freeze_support.py @@ -18,7 +18,8 @@ def freeze_includes() -> List[str]: def _iter_all_modules( - package: Union[str, types.ModuleType], prefix: str = "", + package: Union[str, types.ModuleType], + prefix: str = "", ) -> Iterator[str]: """Iterate over the names of all modules that can be found in the given package, recursively. diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index 22bebf5b783..41c12a2ccd8 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -540,7 +540,8 @@ def pytest_runtest_logreport(report: "TestReport") -> None: @hookspec(firstresult=True) def pytest_report_to_serializable( - config: "Config", report: Union["CollectReport", "TestReport"], + config: "Config", + report: Union["CollectReport", "TestReport"], ) -> Optional[Dict[str, Any]]: """Serialize the given report object into a data structure suitable for sending over the wire, e.g. converted to JSON.""" @@ -548,7 +549,8 @@ def pytest_report_to_serializable( @hookspec(firstresult=True) def pytest_report_from_serializable( - config: "Config", data: Dict[str, Any], + config: "Config", + data: Dict[str, Any], ) -> Optional[Union["CollectReport", "TestReport"]]: """Restore a report object previously serialized with pytest_report_to_serializable().""" @@ -597,7 +599,8 @@ def pytest_sessionstart(session: "Session") -> None: def pytest_sessionfinish( - session: "Session", exitstatus: Union[int, "ExitCode"], + session: "Session", + exitstatus: Union[int, "ExitCode"], ) -> None: """Called after whole test run finished, right before returning the exit status to the system. @@ -701,7 +704,10 @@ def pytest_report_header( def pytest_report_collectionfinish( - config: "Config", startpath: Path, startdir: py.path.local, items: Sequence["Item"], + config: "Config", + startpath: Path, + startdir: py.path.local, + items: Sequence["Item"], ) -> Union[str, List[str]]: """Return a string or list of strings to be displayed after collection has finished successfully. @@ -731,9 +737,7 @@ def pytest_report_collectionfinish( @hookspec(firstresult=True) def pytest_report_teststatus( report: Union["CollectReport", "TestReport"], config: "Config" -) -> Tuple[ - str, str, Union[str, Mapping[str, bool]], -]: +) -> Tuple[str, str, Union[str, Mapping[str, bool]]]: """Return result-category, shortletter and verbose word for status reporting. @@ -758,7 +762,9 @@ def pytest_report_teststatus( def pytest_terminal_summary( - terminalreporter: "TerminalReporter", exitstatus: "ExitCode", config: "Config", + terminalreporter: "TerminalReporter", + exitstatus: "ExitCode", + config: "Config", ) -> None: """Add a section to terminal summary reporting. @@ -865,7 +871,8 @@ def pytest_markeval_namespace(config: "Config") -> Dict[str, Any]: def pytest_internalerror( - excrepr: "ExceptionRepr", excinfo: "ExceptionInfo[BaseException]", + excrepr: "ExceptionRepr", + excinfo: "ExceptionInfo[BaseException]", ) -> Optional[bool]: """Called for internal errors. diff --git a/src/_pytest/junitxml.py b/src/_pytest/junitxml.py index c4761cd3b87..690fd976ca9 100644 --- a/src/_pytest/junitxml.py +++ b/src/_pytest/junitxml.py @@ -486,7 +486,7 @@ def __init__( ) self.node_reporters: Dict[ Tuple[Union[str, TestReport], object], _NodeReporter - ] = ({}) + ] = {} self.node_reporters_ordered: List[_NodeReporter] = [] self.global_properties: List[Tuple[str, str]] = [] diff --git a/src/_pytest/logging.py b/src/_pytest/logging.py index 2e4847328ab..e0d71c7eb54 100644 --- a/src/_pytest/logging.py +++ b/src/_pytest/logging.py @@ -685,9 +685,11 @@ def pytest_runtest_logreport(self) -> None: def _runtest_for(self, item: nodes.Item, when: str) -> Generator[None, None, None]: """Implement the internals of the pytest_runtest_xxx() hooks.""" with catching_logs( - self.caplog_handler, level=self.log_level, + self.caplog_handler, + level=self.log_level, ) as caplog_handler, catching_logs( - self.report_handler, level=self.log_level, + self.report_handler, + level=self.log_level, ) as report_handler: caplog_handler.reset() report_handler.reset() diff --git a/src/_pytest/main.py b/src/_pytest/main.py index 79afdde6155..5036601f9bb 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -116,7 +116,9 @@ def pytest_addoption(parser: Parser) -> None: help="markers not registered in the `markers` section of the configuration file raise errors.", ) group._addoption( - "--strict", action="store_true", help="(deprecated) alias to --strict-markers.", + "--strict", + action="store_true", + help="(deprecated) alias to --strict-markers.", ) group._addoption( "-c", @@ -656,11 +658,11 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: # Keep track of any collected nodes in here, so we don't duplicate fixtures. node_cache1: Dict[Path, Sequence[nodes.Collector]] = {} - node_cache2: Dict[Tuple[Type[nodes.Collector], Path], nodes.Collector] = ({}) + node_cache2: Dict[Tuple[Type[nodes.Collector], Path], nodes.Collector] = {} # Keep track of any collected collectors in matchnodes paths, so they # are not collected more than once. - matchnodes_cache: Dict[Tuple[Type[nodes.Collector], str], CollectReport] = ({}) + matchnodes_cache: Dict[Tuple[Type[nodes.Collector], str], CollectReport] = {} # Dirnames of pkgs with dunder-init files. pkg_roots: Dict[str, Package] = {} diff --git a/src/_pytest/mark/__init__.py b/src/_pytest/mark/__init__.py index 329a11c4ae8..97fb36ef73b 100644 --- a/src/_pytest/mark/__init__.py +++ b/src/_pytest/mark/__init__.py @@ -56,7 +56,10 @@ def param( @pytest.mark.parametrize( "test_input,expected", - [("3+5", 8), pytest.param("6*9", 42, marks=pytest.mark.xfail),], + [ + ("3+5", 8), + pytest.param("6*9", 42, marks=pytest.mark.xfail), + ], ) def test_eval(test_input, expected): assert eval(test_input) == expected diff --git a/src/_pytest/mark/expression.py b/src/_pytest/mark/expression.py index dc3991b10c4..2e7dcf93cd4 100644 --- a/src/_pytest/mark/expression.py +++ b/src/_pytest/mark/expression.py @@ -102,7 +102,8 @@ def lex(self, input: str) -> Iterator[Token]: pos += len(value) else: raise ParseError( - pos + 1, 'unexpected character "{}"'.format(input[pos]), + pos + 1, + 'unexpected character "{}"'.format(input[pos]), ) yield Token(TokenType.EOF, "", pos) @@ -120,7 +121,8 @@ def reject(self, expected: Sequence[TokenType]) -> "NoReturn": raise ParseError( self.current.pos + 1, "expected {}; got {}".format( - " OR ".join(type.value for type in expected), self.current.type.value, + " OR ".join(type.value for type in expected), + self.current.type.value, ), ) @@ -204,7 +206,9 @@ def compile(self, input: str) -> "Expression": """ astexpr = expression(Scanner(input)) code: types.CodeType = compile( - astexpr, filename="", mode="eval", + astexpr, + filename="", + mode="eval", ) return Expression(code) diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index ae6920735a2..d2f21e1684d 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -460,15 +460,11 @@ def __call__( # type: ignore[override] ... class _UsefixturesMarkDecorator(MarkDecorator): - def __call__( # type: ignore[override] - self, *fixtures: str - ) -> MarkDecorator: + def __call__(self, *fixtures: str) -> MarkDecorator: # type: ignore[override] ... class _FilterwarningsMarkDecorator(MarkDecorator): - def __call__( # type: ignore[override] - self, *filters: str - ) -> MarkDecorator: + def __call__(self, *filters: str) -> MarkDecorator: # type: ignore[override] ... diff --git a/src/_pytest/monkeypatch.py b/src/_pytest/monkeypatch.py index d012b8a535a..ffef87173a8 100644 --- a/src/_pytest/monkeypatch.py +++ b/src/_pytest/monkeypatch.py @@ -124,7 +124,7 @@ class MonkeyPatch: def __init__(self) -> None: self._setattr: List[Tuple[object, str, object]] = [] - self._setitem: List[Tuple[MutableMapping[Any, Any], object, object]] = ([]) + self._setitem: List[Tuple[MutableMapping[Any, Any], object, object]] = [] self._cwd: Optional[str] = None self._savesyspath: Optional[List[str]] = None @@ -157,13 +157,21 @@ def test_partial(monkeypatch): @overload def setattr( - self, target: str, name: object, value: Notset = ..., raising: bool = ..., + self, + target: str, + name: object, + value: Notset = ..., + raising: bool = ..., ) -> None: ... @overload def setattr( - self, target: object, name: str, value: object, raising: bool = ..., + self, + target: object, + name: str, + value: object, + raising: bool = ..., ) -> None: ... diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index c6eb49dec4a..2a96d55ad05 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -231,7 +231,10 @@ def warn(self, warning: Warning) -> None: path, lineno = get_fslocation_from_item(self) assert lineno is not None warnings.warn_explicit( - warning, category=None, filename=str(path), lineno=lineno + 1, + warning, + category=None, + filename=str(path), + lineno=lineno + 1, ) # Methods for ordering nodes. diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 0d1f8f278f9..4544d2c2bbb 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -291,13 +291,15 @@ def getcall(self, name: str) -> ParsedCall: @overload def getreports( - self, names: "Literal['pytest_collectreport']", + self, + names: "Literal['pytest_collectreport']", ) -> Sequence[CollectReport]: ... @overload def getreports( - self, names: "Literal['pytest_runtest_logreport']", + self, + names: "Literal['pytest_runtest_logreport']", ) -> Sequence[TestReport]: ... @@ -354,13 +356,15 @@ def matchreport( @overload def getfailures( - self, names: "Literal['pytest_collectreport']", + self, + names: "Literal['pytest_collectreport']", ) -> Sequence[CollectReport]: ... @overload def getfailures( - self, names: "Literal['pytest_runtest_logreport']", + self, + names: "Literal['pytest_runtest_logreport']", ) -> Sequence[TestReport]: ... @@ -419,7 +423,10 @@ def assertoutcome(self, passed: int = 0, skipped: int = 0, failed: int = 0) -> N outcomes = self.listoutcomes() assertoutcome( - outcomes, passed=passed, skipped=skipped, failed=failed, + outcomes, + passed=passed, + skipped=skipped, + failed=failed, ) def clear(self) -> None: @@ -659,7 +666,7 @@ def __init__( self._request = request self._mod_collections: WeakKeyDictionary[ Collector, List[Union[Item, Collector]] - ] = (WeakKeyDictionary()) + ] = WeakKeyDictionary() if request.function: name: str = request.function.__name__ else: diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 31d91853f2f..50ea60c2dff 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -1202,7 +1202,9 @@ def _validate_ids( return new_ids def _resolve_arg_value_types( - self, argnames: Sequence[str], indirect: Union[bool, Sequence[str]], + self, + argnames: Sequence[str], + indirect: Union[bool, Sequence[str]], ) -> Dict[str, "Literal['params', 'funcargs']"]: """Resolve if each parametrized argument must be considered a parameter to a fixture or a "funcarg" to the function, based on the @@ -1240,7 +1242,9 @@ def _resolve_arg_value_types( return valtypes def _validate_if_using_arg_names( - self, argnames: Sequence[str], indirect: Union[bool, Sequence[str]], + self, + argnames: Sequence[str], + indirect: Union[bool, Sequence[str]], ) -> None: """Check if all argnames are being used, by default values, or directly/indirectly. @@ -1691,7 +1695,8 @@ def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None: # TODO: Type ignored -- breaks Liskov Substitution. def repr_failure( # type: ignore[override] - self, excinfo: ExceptionInfo[BaseException], + self, + excinfo: ExceptionInfo[BaseException], ) -> Union[str, TerminalRepr]: style = self.config.getoption("tbstyle", "auto") if style == "auto": diff --git a/src/_pytest/reports.py b/src/_pytest/reports.py index 58f12517c5b..bcd40fb362e 100644 --- a/src/_pytest/reports.py +++ b/src/_pytest/reports.py @@ -307,7 +307,7 @@ def from_item_and_call(cls, item: Item, call: "CallInfo[None]") -> "TestReport": Tuple[str, int, str], str, TerminalRepr, - ] = (None) + ] = None else: if not isinstance(excinfo, ExceptionInfo): outcome = "failed" diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index d3d1a4b666e..eea9214e70f 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -468,7 +468,9 @@ def pytest_internalerror(self, excrepr: ExceptionRepr) -> bool: return True def pytest_warning_recorded( - self, warning_message: warnings.WarningMessage, nodeid: str, + self, + warning_message: warnings.WarningMessage, + nodeid: str, ) -> None: from _pytest.warnings import warning_record_to_str @@ -1306,7 +1308,8 @@ def _get_line_with_reprcrash_message( def _folded_skips( - startpath: Path, skipped: Sequence[CollectReport], + startpath: Path, + skipped: Sequence[CollectReport], ) -> List[Tuple[int, str, Optional[int], str]]: d: Dict[Tuple[str, Optional[int], str], List[CollectReport]] = {} for event in skipped: diff --git a/src/_pytest/threadexception.py b/src/_pytest/threadexception.py index 1c1f62fdb73..d084dc6e6a2 100644 --- a/src/_pytest/threadexception.py +++ b/src/_pytest/threadexception.py @@ -69,7 +69,9 @@ def thread_exception_runtest_hook() -> Generator[None, None, None]: msg = f"Exception in thread {thread_name}\n\n" msg += "".join( traceback.format_exception( - cm.args.exc_type, cm.args.exc_value, cm.args.exc_traceback, + cm.args.exc_type, + cm.args.exc_value, + cm.args.exc_traceback, ) ) warnings.warn(pytest.PytestUnhandledThreadExceptionWarning(msg)) diff --git a/src/_pytest/tmpdir.py b/src/_pytest/tmpdir.py index 08c445e2bf8..29c7e19d7b4 100644 --- a/src/_pytest/tmpdir.py +++ b/src/_pytest/tmpdir.py @@ -53,7 +53,10 @@ def __init__( @classmethod def from_config( - cls, config: Config, *, _ispytest: bool = False, + cls, + config: Config, + *, + _ispytest: bool = False, ) -> "TempPathFactory": """Create a factory according to pytest configuration. diff --git a/testing/example_scripts/dataclasses/test_compare_recursive_dataclasses.py b/testing/example_scripts/dataclasses/test_compare_recursive_dataclasses.py index 167140e16a6..0945790f004 100644 --- a/testing/example_scripts/dataclasses/test_compare_recursive_dataclasses.py +++ b/testing/example_scripts/dataclasses/test_compare_recursive_dataclasses.py @@ -29,10 +29,16 @@ class C3: def test_recursive_dataclasses(): left = C3( - S(10, "ten"), C2(C(S(1, "one"), S(2, "two")), S(2, "three")), "equal", "left", + S(10, "ten"), + C2(C(S(1, "one"), S(2, "two")), S(2, "three")), + "equal", + "left", ) right = C3( - S(20, "xxx"), C2(C(S(1, "one"), S(2, "yyy")), S(3, "three")), "equal", "right", + S(20, "xxx"), + C2(C(S(1, "one"), S(2, "yyy")), S(3, "three")), + "equal", + "right", ) assert left == right diff --git a/testing/io/test_terminalwriter.py b/testing/io/test_terminalwriter.py index fac7593eadd..4866c94a558 100644 --- a/testing/io/test_terminalwriter.py +++ b/testing/io/test_terminalwriter.py @@ -258,13 +258,22 @@ def test_combining(self) -> None: id="with markup and code_highlight", ), pytest.param( - True, False, "assert 0\n", id="with markup but no code_highlight", + True, + False, + "assert 0\n", + id="with markup but no code_highlight", ), pytest.param( - False, True, "assert 0\n", id="without markup but with code_highlight", + False, + True, + "assert 0\n", + id="without markup but with code_highlight", ), pytest.param( - False, False, "assert 0\n", id="neither markup nor code_highlight", + False, + False, + "assert 0\n", + id="neither markup nor code_highlight", ), ], ) diff --git a/testing/python/approx.py b/testing/python/approx.py index e76d6b774d6..db6124e3914 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -514,7 +514,8 @@ def test_foo(): ) def test_expected_value_type_error(self, x, name): with pytest.raises( - TypeError, match=fr"pytest.approx\(\) does not support nested {name}:", + TypeError, + match=fr"pytest.approx\(\) does not support nested {name}:", ): approx(x) diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index 58a902a3a59..6577ff18ebf 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -514,7 +514,10 @@ def getini(self, name): ] for config, expected in values: result = idmaker( - ("a",), [pytest.param("string")], idfn=lambda _: "ação", config=config, + ("a",), + [pytest.param("string")], + idfn=lambda _: "ação", + config=config, ) assert result == [expected] @@ -546,7 +549,10 @@ def getini(self, name): ] for config, expected in values: result = idmaker( - ("a",), [pytest.param("string")], ids=["ação"], config=config, + ("a",), + [pytest.param("string")], + ids=["ação"], + config=config, ) assert result == [expected] diff --git a/testing/test_capture.py b/testing/test_capture.py index 3a5c617fe5a..4d89f0b9e40 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -1431,7 +1431,8 @@ def test_capattr(): @pytest.mark.skipif( - not sys.platform.startswith("win"), reason="only on windows", + not sys.platform.startswith("win"), + reason="only on windows", ) def test_py36_windowsconsoleio_workaround_non_standard_streams() -> None: """ diff --git a/testing/test_debugging.py b/testing/test_debugging.py index e1b57299d25..9cdd411667d 100644 --- a/testing/test_debugging.py +++ b/testing/test_debugging.py @@ -877,7 +877,9 @@ def test_pdb_custom_cls_without_pdb( assert custom_pdb_calls == [] def test_pdb_custom_cls_with_set_trace( - self, pytester: Pytester, monkeypatch: MonkeyPatch, + self, + pytester: Pytester, + monkeypatch: MonkeyPatch, ) -> None: pytester.makepyfile( custom_pdb=""" diff --git a/testing/test_doctest.py b/testing/test_doctest.py index 08d0aacf68c..b63665349a1 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -69,7 +69,9 @@ def my_func(): @pytest.mark.parametrize("filename", ["__init__", "whatever"]) def test_collect_module_two_doctest_no_modulelevel( - self, pytester: Pytester, filename: str, + self, + pytester: Pytester, + filename: str, ) -> None: path = pytester.makepyfile( **{ diff --git a/testing/test_mark.py b/testing/test_mark.py index 5f4b3e063e4..420faf91ec9 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -356,8 +356,14 @@ def test_func(arg): "foo or or", "at column 8: expected not OR left parenthesis OR identifier; got or", ), - ("(foo", "at column 5: expected right parenthesis; got end of input",), - ("foo bar", "at column 5: expected end of input; got identifier",), + ( + "(foo", + "at column 5: expected right parenthesis; got end of input", + ), + ( + "foo bar", + "at column 5: expected end of input; got identifier", + ), ( "or or", "at column 1: expected not OR left parenthesis OR identifier; got or", @@ -863,7 +869,8 @@ def test_one(): assert passed + skipped + failed == 0 @pytest.mark.parametrize( - "keyword", ["__", "+", ".."], + "keyword", + ["__", "+", ".."], ) def test_no_magic_values(self, pytester: Pytester, keyword: str) -> None: """Make sure the tests do not match on magic values, diff --git a/testing/test_mark_expression.py b/testing/test_mark_expression.py index faca02d9330..d37324f51cd 100644 --- a/testing/test_mark_expression.py +++ b/testing/test_mark_expression.py @@ -70,7 +70,11 @@ def test_syntax_oddeties(expr: str, expected: bool) -> None: ("expr", "column", "message"), ( ("(", 2, "expected not OR left parenthesis OR identifier; got end of input"), - (" (", 3, "expected not OR left parenthesis OR identifier; got end of input",), + ( + " (", + 3, + "expected not OR left parenthesis OR identifier; got end of input", + ), ( ")", 1, @@ -81,7 +85,11 @@ def test_syntax_oddeties(expr: str, expected: bool) -> None: 1, "expected not OR left parenthesis OR identifier; got right parenthesis", ), - ("not", 4, "expected not OR left parenthesis OR identifier; got end of input",), + ( + "not", + 4, + "expected not OR left parenthesis OR identifier; got end of input", + ), ( "not not", 8, @@ -98,7 +106,11 @@ def test_syntax_oddeties(expr: str, expected: bool) -> None: 10, "expected not OR left parenthesis OR identifier; got end of input", ), - ("ident and or", 11, "expected not OR left parenthesis OR identifier; got or",), + ( + "ident and or", + 11, + "expected not OR left parenthesis OR identifier; got or", + ), ("ident ident", 7, "expected end of input; got identifier"), ), ) diff --git a/testing/test_monkeypatch.py b/testing/test_monkeypatch.py index 0b97a0e5adb..95521818021 100644 --- a/testing/test_monkeypatch.py +++ b/testing/test_monkeypatch.py @@ -348,7 +348,9 @@ class SampleInherit(Sample): @pytest.mark.parametrize( - "Sample", [Sample, SampleInherit], ids=["new", "new-inherit"], + "Sample", + [Sample, SampleInherit], + ids=["new", "new-inherit"], ) def test_issue156_undo_staticmethod(Sample: Type[Sample]) -> None: monkeypatch = MonkeyPatch() diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py index a5282a50795..9835b24a046 100644 --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -346,7 +346,9 @@ def test_import_plugin_dotted_name( assert mod.x == 3 def test_consider_conftest_deps( - self, pytester: Pytester, pytestpm: PytestPluginManager, + self, + pytester: Pytester, + pytestpm: PytestPluginManager, ) -> None: mod = import_path(pytester.makepyfile("pytest_plugins='xyz'")) with pytest.raises(ImportError): diff --git a/testing/test_runner_xunit.py b/testing/test_runner_xunit.py index e90d761f633..e077ac41e2c 100644 --- a/testing/test_runner_xunit.py +++ b/testing/test_runner_xunit.py @@ -242,7 +242,9 @@ def test_function2(hello): @pytest.mark.parametrize("arg", ["", "arg"]) def test_setup_teardown_function_level_with_optional_argument( - pytester: Pytester, monkeypatch, arg: str, + pytester: Pytester, + monkeypatch, + arg: str, ) -> None: """Parameter to setup/teardown xunit-style functions parameter is now optional (#1728).""" import sys diff --git a/testing/test_stepwise.py b/testing/test_stepwise.py index ff2ec16b707..85489fce803 100644 --- a/testing/test_stepwise.py +++ b/testing/test_stepwise.py @@ -138,7 +138,12 @@ def test_fail_and_continue_with_stepwise(stepwise_pytester: Pytester) -> None: @pytest.mark.parametrize("stepwise_skip", ["--stepwise-skip", "--sw-skip"]) def test_run_with_skip_option(stepwise_pytester: Pytester, stepwise_skip: str) -> None: result = stepwise_pytester.runpytest( - "-v", "--strict-markers", "--stepwise", stepwise_skip, "--fail", "--fail-last", + "-v", + "--strict-markers", + "--stepwise", + stepwise_skip, + "--fail", + "--fail-last", ) assert _strip_resource_warnings(result.stderr.lines) == [] diff --git a/testing/test_warnings.py b/testing/test_warnings.py index 574f3f1ec02..8c9c227b26a 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -662,7 +662,7 @@ def capwarn(self, pytester: Pytester): class CapturedWarnings: captured: List[ Tuple[warnings.WarningMessage, Optional[Tuple[str, int, str]]] - ] = ([]) + ] = [] @classmethod def pytest_warning_recorded(cls, warning_message, when, nodeid, location): From 48c9a96a03261e7cfa5aad0367a9186d9032904a Mon Sep 17 00:00:00 2001 From: Anton <44246099+antonblr@users.noreply.github.com> Date: Wed, 30 Dec 2020 19:00:37 -0800 Subject: [PATCH 0048/2772] Fix failing staticmethod tests if they are inherited (#8205) * Fix failing staticmethod tests if they are inherited * add comments, set default=None --- AUTHORS | 1 + changelog/8061.bugfix.rst | 1 + src/_pytest/compat.py | 7 ++++++- testing/python/fixtures.py | 14 ++++++++++++++ 4 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 changelog/8061.bugfix.rst diff --git a/AUTHORS b/AUTHORS index 20798f3093d..abac9f010a1 100644 --- a/AUTHORS +++ b/AUTHORS @@ -29,6 +29,7 @@ Andy Freeland Anthon van der Neut Anthony Shaw Anthony Sottile +Anton Grinevich Anton Lodder Antony Lee Arel Cordero diff --git a/changelog/8061.bugfix.rst b/changelog/8061.bugfix.rst new file mode 100644 index 00000000000..2c8980fb34e --- /dev/null +++ b/changelog/8061.bugfix.rst @@ -0,0 +1 @@ +Fixed failing staticmethod test cases if they are inherited from a parent test class. diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index 0b87c7bbc08..b354fcb3f63 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -163,7 +163,12 @@ def getfuncargnames( # it's passed as an unbound method or function, remove the first # parameter name. if is_method or ( - cls and not isinstance(cls.__dict__.get(name, None), staticmethod) + # Not using `getattr` because we don't want to resolve the staticmethod. + # Not using `cls.__dict__` because we want to check the entire MRO. + cls + and not isinstance( + inspect.getattr_static(cls, name, default=None), staticmethod + ) ): arg_names = arg_names[1:] # Remove any names that will be replaced with mocks. diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index 862a65abe10..12340e690eb 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -59,6 +59,20 @@ def static(arg1, arg2, x=1): assert getfuncargnames(A.static, cls=A) == ("arg1", "arg2") +def test_getfuncargnames_staticmethod_inherited() -> None: + """Test getfuncargnames for inherited staticmethods (#8061)""" + + class A: + @staticmethod + def static(arg1, arg2, x=1): + raise NotImplementedError() + + class B(A): + pass + + assert getfuncargnames(B.static, cls=B) == ("arg1", "arg2") + + def test_getfuncargnames_partial(): """Check getfuncargnames for methods defined with functools.partial (#5701)""" import functools From 5f11a35b99e3606b003f695b908496c363796074 Mon Sep 17 00:00:00 2001 From: mefmund Date: Thu, 31 Dec 2020 19:25:44 +0100 Subject: [PATCH 0049/2772] Add missing fixture (#8207) Co-authored-by: Florian Bruhin Co-authored-by: Bruno Oliveira --- doc/en/fixture.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index 752385adc89..a629bb7d47f 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -879,9 +879,9 @@ Here's what that might look like: admin_client.delete_user(user) - def test_email_received(receiving_user, email): + def test_email_received(sending_user, receiving_user, email): email = Email(subject="Hey!", body="How's it going?") - sending_user.send_email(_email, receiving_user) + sending_user.send_email(email, receiving_user) assert email in receiving_user.inbox Because ``receiving_user`` is the last fixture to run during setup, it's the first to run From 7d306e9e86f114a812bcf234211ff7b8533d11f9 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 30 Dec 2020 14:37:00 +0200 Subject: [PATCH 0050/2772] reports: BaseReport.{passed,failed,skipped} more friendly to mypy Not smart enough to understand the previous code. --- src/_pytest/reports.py | 17 +++++++++++++---- testing/test_terminal.py | 6 +++--- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/_pytest/reports.py b/src/_pytest/reports.py index bcd40fb362e..c78d5423974 100644 --- a/src/_pytest/reports.py +++ b/src/_pytest/reports.py @@ -65,6 +65,7 @@ class BaseReport: ] sections: List[Tuple[str, str]] nodeid: str + outcome: "Literal['passed', 'failed', 'skipped']" def __init__(self, **kw: Any) -> None: self.__dict__.update(kw) @@ -141,9 +142,17 @@ def capstderr(self) -> str: content for (prefix, content) in self.get_sections("Captured stderr") ) - passed = property(lambda x: x.outcome == "passed") - failed = property(lambda x: x.outcome == "failed") - skipped = property(lambda x: x.outcome == "skipped") + @property + def passed(self) -> bool: + return self.outcome == "passed" + + @property + def failed(self) -> bool: + return self.outcome == "failed" + + @property + def skipped(self) -> bool: + return self.outcome == "skipped" @property def fspath(self) -> str: @@ -348,7 +357,7 @@ class CollectReport(BaseReport): def __init__( self, nodeid: str, - outcome: "Literal['passed', 'skipped', 'failed']", + outcome: "Literal['passed', 'failed', 'skipped']", longrepr, result: Optional[List[Union[Item, Collector]]], sections: Iterable[Tuple[str, str]] = (), diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 6d0a23fe0f1..e536f70989c 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -2230,19 +2230,19 @@ class X: ev1 = cast(CollectReport, X()) ev1.when = "execute" - ev1.skipped = True + ev1.skipped = True # type: ignore[misc] ev1.longrepr = longrepr ev2 = cast(CollectReport, X()) ev2.when = "execute" ev2.longrepr = longrepr - ev2.skipped = True + ev2.skipped = True # type: ignore[misc] # ev3 might be a collection report ev3 = cast(CollectReport, X()) ev3.when = "collect" ev3.longrepr = longrepr - ev3.skipped = True + ev3.skipped = True # type: ignore[misc] values = _folded_skips(Path.cwd(), [ev1, ev2, ev3]) assert len(values) == 1 From 73c410523097a699559ce5ae12b9caf9c50972fc Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 30 Dec 2020 14:44:43 +0200 Subject: [PATCH 0051/2772] reports: improve a type annotation --- src/_pytest/reports.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/_pytest/reports.py b/src/_pytest/reports.py index c78d5423974..d2d7115b2e5 100644 --- a/src/_pytest/reports.py +++ b/src/_pytest/reports.py @@ -358,7 +358,9 @@ def __init__( self, nodeid: str, outcome: "Literal['passed', 'failed', 'skipped']", - longrepr, + longrepr: Union[ + None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr + ], result: Optional[List[Union[Item, Collector]]], sections: Iterable[Tuple[str, str]] = (), **extra, From 20c59e3aa4446faf2fd1ac04322e8076c2aebe3f Mon Sep 17 00:00:00 2001 From: sousajf1 Date: Fri, 1 Jan 2021 15:25:11 +0000 Subject: [PATCH 0052/2772] pytest-dev#8204 migrate some tests to tmp_path fixture (#8209) migrating some tests from tmpdir to tmp_path fixture --- testing/code/test_excinfo.py | 6 +++--- testing/test_argcomplete.py | 17 +++++++++++------ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index 19c888403f2..e6a9cbaf737 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -364,12 +364,12 @@ def test_excinfo_no_sourcecode(): assert s == " File '':1 in \n ???\n" -def test_excinfo_no_python_sourcecode(tmpdir): +def test_excinfo_no_python_sourcecode(tmp_path: Path) -> None: # XXX: simplified locally testable version - tmpdir.join("test.txt").write("{{ h()}}:") + tmp_path.joinpath("test.txt").write_text("{{ h()}}:") jinja2 = pytest.importorskip("jinja2") - loader = jinja2.FileSystemLoader(str(tmpdir)) + loader = jinja2.FileSystemLoader(str(tmp_path)) env = jinja2.Environment(loader=loader) template = env.get_template("test.txt") excinfo = pytest.raises(ValueError, template.render, h=h) diff --git a/testing/test_argcomplete.py b/testing/test_argcomplete.py index a3224be5126..8c10e230b0c 100644 --- a/testing/test_argcomplete.py +++ b/testing/test_argcomplete.py @@ -1,7 +1,9 @@ import subprocess import sys +from pathlib import Path import pytest +from _pytest.monkeypatch import MonkeyPatch # Test for _argcomplete but not specific for any application. @@ -65,19 +67,22 @@ def __call__(self, prefix, **kwargs): class TestArgComplete: @pytest.mark.skipif("sys.platform in ('win32', 'darwin')") - def test_compare_with_compgen(self, tmpdir): + def test_compare_with_compgen( + self, tmp_path: Path, monkeypatch: MonkeyPatch + ) -> None: from _pytest._argcomplete import FastFilesCompleter ffc = FastFilesCompleter() fc = FilesCompleter() - with tmpdir.as_cwd(): - assert equal_with_bash("", ffc, fc, out=sys.stdout) + monkeypatch.chdir(tmp_path) - tmpdir.ensure("data") + assert equal_with_bash("", ffc, fc, out=sys.stdout) - for x in ["d", "data", "doesnotexist", ""]: - assert equal_with_bash(x, ffc, fc, out=sys.stdout) + tmp_path.cwd().joinpath("data").touch() + + for x in ["d", "data", "doesnotexist", ""]: + assert equal_with_bash(x, ffc, fc, out=sys.stdout) @pytest.mark.skipif("sys.platform in ('win32', 'darwin')") def test_remove_dir_prefix(self): From ac428f67ebb2469d91476cbe8ec7e10da6f6b106 Mon Sep 17 00:00:00 2001 From: sousajo Date: Fri, 1 Jan 2021 16:55:03 +0000 Subject: [PATCH 0053/2772] pytest-dev#8204 migrate tests on testing/code/test_source to tmp_path --- testing/code/test_source.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/testing/code/test_source.py b/testing/code/test_source.py index 6b8443fd243..083a7911f55 100644 --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -17,6 +17,7 @@ from _pytest._code import Frame from _pytest._code import getfslineno from _pytest._code import Source +from _pytest.pathlib import import_path def test_source_str_function() -> None: @@ -285,7 +286,9 @@ def g(): assert lines == ["def f():", " def g():", " pass"] -def test_source_of_class_at_eof_without_newline(tmpdir, _sys_snapshot) -> None: +def test_source_of_class_at_eof_without_newline( + tmpdir, _sys_snapshot, tmp_path: Path +) -> None: # this test fails because the implicit inspect.getsource(A) below # does not return the "x = 1" last line. source = Source( @@ -295,9 +298,10 @@ def method(self): x = 1 """ ) - path = tmpdir.join("a.py") - path.write(source) - s2 = Source(tmpdir.join("a.py").pyimport().A) + path = tmp_path.joinpath("a.py") + path.write_text(str(source)) + mod: Any = import_path(path) + s2 = Source(mod.A) assert str(source).strip() == str(s2).strip() From b02f1c8ae74d8c5273e3d651539e931af64c34fa Mon Sep 17 00:00:00 2001 From: Hong Xu Date: Fri, 1 Jan 2021 12:21:39 -0800 Subject: [PATCH 0054/2772] DOC: Update multiple references to testdir to pytester In https://docs.pytest.org/en/stable/reference.html#testdir, it is suggested: > New code should avoid using testdir in favor of pytester. Multiple spots in the documents still use testdir and they can be quite confusing (especially the plugin writing guide). --- CONTRIBUTING.rst | 14 +++++++------- doc/en/writing_plugins.rst | 18 +++++++++--------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 2669cb19509..054f809a818 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -324,20 +324,20 @@ Here is a simple overview, with pytest-specific bits: Writing Tests ~~~~~~~~~~~~~ -Writing tests for plugins or for pytest itself is often done using the `testdir fixture `_, as a "black-box" test. +Writing tests for plugins or for pytest itself is often done using the `pytester fixture `_, as a "black-box" test. For example, to ensure a simple test passes you can write: .. code-block:: python - def test_true_assertion(testdir): - testdir.makepyfile( + def test_true_assertion(pytester): + pytester.makepyfile( """ def test_foo(): assert True """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.assert_outcomes(failed=0, passed=1) @@ -346,14 +346,14 @@ Alternatively, it is possible to make checks based on the actual output of the t .. code-block:: python - def test_true_assertion(testdir): - testdir.makepyfile( + def test_true_assertion(pytester): + pytester.makepyfile( """ def test_foo(): assert False """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*assert False*", "*1 failed*"]) When choosing a file where to write a new test, take a look at the existing files and see if there's diff --git a/doc/en/writing_plugins.rst b/doc/en/writing_plugins.rst index f53f561cfad..e9806a6664d 100644 --- a/doc/en/writing_plugins.rst +++ b/doc/en/writing_plugins.rst @@ -337,7 +337,7 @@ testing directory: Alternatively you can invoke pytest with the ``-p pytester`` command line option. -This will allow you to use the :py:class:`testdir <_pytest.pytester.Testdir>` +This will allow you to use the :py:class:`pytester <_pytest.pytester.Pytester>` fixture for testing your plugin code. Let's demonstrate what you can do with the plugin with an example. Imagine we @@ -374,17 +374,17 @@ string value of ``Hello World!`` if we do not supply a value or ``Hello return _hello -Now the ``testdir`` fixture provides a convenient API for creating temporary +Now the ``pytester`` fixture provides a convenient API for creating temporary ``conftest.py`` files and test files. It also allows us to run the tests and return a result object, with which we can assert the tests' outcomes. .. code-block:: python - def test_hello(testdir): + def test_hello(pytester): """Make sure that our plugin works.""" # create a temporary conftest.py file - testdir.makeconftest( + pytester.makeconftest( """ import pytest @@ -399,7 +399,7 @@ return a result object, with which we can assert the tests' outcomes. ) # create a temporary pytest test file - testdir.makepyfile( + pytester.makepyfile( """ def test_hello_default(hello): assert hello() == "Hello World!" @@ -410,7 +410,7 @@ return a result object, with which we can assert the tests' outcomes. ) # run all tests with pytest - result = testdir.runpytest() + result = pytester.runpytest() # check that all 4 tests passed result.assert_outcomes(passed=4) @@ -430,9 +430,9 @@ Additionally it is possible to copy examples for an example folder before runnin # content of test_example.py - def test_plugin(testdir): - testdir.copy_example("test_example.py") - testdir.runpytest("-k", "test_example") + def test_plugin(pytester): + pytester.copy_example("test_example.py") + pytester.runpytest("-k", "test_example") def test_example(): From 6c575ad8c8aa298a8e8d11612d837c51880d528a Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 1 Jan 2021 14:42:14 +0200 Subject: [PATCH 0055/2772] fixtures: simplify FixtureRequest._get_fixturestack() --- src/_pytest/fixtures.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 53f33d3e13d..aed81029f44 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -607,14 +607,11 @@ def _get_active_fixturedef( def _get_fixturestack(self) -> List["FixtureDef[Any]"]: current = self values: List[FixtureDef[Any]] = [] - while 1: - fixturedef = getattr(current, "_fixturedef", None) - if fixturedef is None: - values.reverse() - return values - values.append(fixturedef) - assert isinstance(current, SubRequest) + while isinstance(current, SubRequest): + values.append(current._fixturedef) # type: ignore[has-type] current = current._parent_request + values.reverse() + return values def _compute_fixture_value(self, fixturedef: "FixtureDef[object]") -> None: """Create a SubRequest based on "self" and call the execute method From ade253c7906b082add837fbac8c193ec85847fbc Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 1 Jan 2021 23:18:17 +0200 Subject: [PATCH 0056/2772] fixtures: type annotate FixtureRequest.keywords --- src/_pytest/fixtures.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index aed81029f44..5bdee3096b1 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -16,6 +16,7 @@ from typing import Iterable from typing import Iterator from typing import List +from typing import MutableMapping from typing import Optional from typing import overload from typing import Sequence @@ -525,9 +526,10 @@ def fspath(self) -> py.path.local: return self._pyfuncitem.fspath # type: ignore @property - def keywords(self): + def keywords(self) -> MutableMapping[str, Any]: """Keywords/markers dictionary for the underlying node.""" - return self.node.keywords + node: nodes.Node = self.node + return node.keywords @property def session(self) -> "Session": From 8ee6d0a8666f91c9c537afacbe3c61f54e342f28 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Thu, 26 Nov 2020 14:50:44 +0200 Subject: [PATCH 0057/2772] Always use getfixturemarker() to access _pytestfixturefunction Keep knowledge of how the marker is stored encapsulated in one place. --- src/_pytest/nose.py | 24 +++++++++++++++--------- testing/python/integration.py | 4 +++- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/_pytest/nose.py b/src/_pytest/nose.py index bb8f99772ac..de91af85af6 100644 --- a/src/_pytest/nose.py +++ b/src/_pytest/nose.py @@ -2,11 +2,12 @@ from _pytest import python from _pytest import unittest from _pytest.config import hookimpl +from _pytest.fixtures import getfixturemarker from _pytest.nodes import Item @hookimpl(trylast=True) -def pytest_runtest_setup(item): +def pytest_runtest_setup(item) -> None: if is_potential_nosetest(item): if not call_optional(item.obj, "setup"): # Call module level setup if there is no object level one. @@ -15,7 +16,7 @@ def pytest_runtest_setup(item): item.session._setupstate.addfinalizer((lambda: teardown_nose(item)), item) -def teardown_nose(item): +def teardown_nose(item) -> None: if is_potential_nosetest(item): if not call_optional(item.obj, "teardown"): call_optional(item.parent.obj, "teardown") @@ -29,11 +30,16 @@ def is_potential_nosetest(item: Item) -> bool: ) -def call_optional(obj, name): +def call_optional(obj: object, name: str) -> bool: method = getattr(obj, name, None) - isfixture = hasattr(method, "_pytestfixturefunction") - if method is not None and not isfixture and callable(method): - # If there's any problems allow the exception to raise rather than - # silently ignoring them. - method() - return True + if method is None: + return False + is_fixture = getfixturemarker(method) is not None + if is_fixture: + return False + if not callable(method): + return False + # If there are any problems allow the exception to raise rather than + # silently ignoring it. + method() + return True diff --git a/testing/python/integration.py b/testing/python/integration.py index 5dce6bdca28..8576fcee341 100644 --- a/testing/python/integration.py +++ b/testing/python/integration.py @@ -3,6 +3,7 @@ import pytest from _pytest import runner from _pytest._code import getfslineno +from _pytest.fixtures import getfixturemarker from _pytest.pytester import Pytester @@ -334,7 +335,8 @@ def test_fix(fix): def test_pytestconfig_is_session_scoped() -> None: from _pytest.fixtures import pytestconfig - marker = pytestconfig._pytestfixturefunction # type: ignore + marker = getfixturemarker(pytestconfig) + assert marker is not None assert marker.scope == "session" From 2ff88098a7340142ed2c6d2c090b8c9fac001a5e Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 6 Oct 2020 20:19:52 +0300 Subject: [PATCH 0058/2772] python: inline a simple method I don't think it adds much value! --- src/_pytest/python.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 50ea60c2dff..29ebd176bbb 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -922,10 +922,6 @@ def copy(self) -> "CallSpec2": cs._idlist = list(self._idlist) return cs - def _checkargnotcontained(self, arg: str) -> None: - if arg in self.params or arg in self.funcargs: - raise ValueError(f"duplicate {arg!r}") - def getparam(self, name: str) -> object: try: return self.params[name] @@ -947,7 +943,8 @@ def setmulti2( param_index: int, ) -> None: for arg, val in zip(argnames, valset): - self._checkargnotcontained(arg) + if arg in self.params or arg in self.funcargs: + raise ValueError(f"duplicate {arg!r}") valtype_for_arg = valtypes[arg] if valtype_for_arg == "params": self.params[arg] = val From 14b5f5e528e52e22d05f086060c5f0dc08a6b37b Mon Sep 17 00:00:00 2001 From: Hong Xu Date: Sat, 2 Jan 2021 00:34:52 -0800 Subject: [PATCH 0059/2772] DOC: Mark pytest module Pytest document currently does not index the top-level package name `pytest`, which causes some trouble when building documentation that cross-refers to the pytest package via ``:mod:`pytest` ``. --- doc/en/reference.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/en/reference.rst b/doc/en/reference.rst index bc6c5670a5c..51c52b33ae9 100644 --- a/doc/en/reference.rst +++ b/doc/en/reference.rst @@ -3,6 +3,8 @@ API Reference ============= +.. module:: pytest + This page contains the full reference to pytest's API. .. contents:: From 8e00df4c4b62f08df0003e578c581e0b5728e571 Mon Sep 17 00:00:00 2001 From: bengartner Date: Mon, 4 Jan 2021 07:58:11 -0600 Subject: [PATCH 0060/2772] Add dot prefix if file makefile extension is invalid for pathlib (#8222) --- AUTHORS | 1 + changelog/8192.bugfix.rst | 3 +++ src/_pytest/pytester.py | 14 ++++++++++++++ testing/test_pytester.py | 31 +++++++++++++++++++++++++++++++ 4 files changed, 49 insertions(+) create mode 100644 changelog/8192.bugfix.rst diff --git a/AUTHORS b/AUTHORS index abac9f010a1..e75baa8cd92 100644 --- a/AUTHORS +++ b/AUTHORS @@ -40,6 +40,7 @@ Aron Curzon Aviral Verma Aviv Palivoda Barney Gale +Ben Gartner Ben Webb Benjamin Peterson Bernard Pratz diff --git a/changelog/8192.bugfix.rst b/changelog/8192.bugfix.rst new file mode 100644 index 00000000000..5b26ecbe45c --- /dev/null +++ b/changelog/8192.bugfix.rst @@ -0,0 +1,3 @@ +``testdir.makefile` now silently accepts values which don't start with ``.`` to maintain backward compatibility with older pytest versions. + +``pytester.makefile`` now issues a clearer error if the ``.`` is missing in the ``ext`` argument. diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 4544d2c2bbb..95b22b3b23e 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -64,6 +64,7 @@ from _pytest.tmpdir import TempPathFactory from _pytest.warning_types import PytestWarning + if TYPE_CHECKING: from typing_extensions import Literal @@ -750,6 +751,11 @@ def _makefile( ) -> Path: items = list(files.items()) + if ext and not ext.startswith("."): + raise ValueError( + f"pytester.makefile expects a file extension, try .{ext} instead of {ext}" + ) + def to_text(s: Union[Any, bytes]) -> str: return s.decode(encoding) if isinstance(s, bytes) else str(s) @@ -1559,6 +1565,14 @@ def finalize(self) -> None: def makefile(self, ext, *args, **kwargs) -> py.path.local: """See :meth:`Pytester.makefile`.""" + if ext and not ext.startswith("."): + # pytester.makefile is going to throw a ValueError in a way that + # testdir.makefile did not, because + # pathlib.Path is stricter suffixes than py.path + # This ext arguments is likely user error, but since testdir has + # allowed this, we will prepend "." as a workaround to avoid breaking + # testdir usage that worked before + ext = "." + ext return py.path.local(str(self._pytester.makefile(ext, *args, **kwargs))) def makeconftest(self, source) -> py.path.local: diff --git a/testing/test_pytester.py b/testing/test_pytester.py index 57d6f4fd9eb..5823d51155c 100644 --- a/testing/test_pytester.py +++ b/testing/test_pytester.py @@ -17,6 +17,7 @@ from _pytest.pytester import Pytester from _pytest.pytester import SysModulesSnapshot from _pytest.pytester import SysPathsSnapshot +from _pytest.pytester import Testdir def test_make_hook_recorder(pytester: Pytester) -> None: @@ -816,3 +817,33 @@ def test_makefile_joins_absolute_path(pytester: Pytester) -> None: def test_testtmproot(testdir) -> None: """Check test_tmproot is a py.path attribute for backward compatibility.""" assert testdir.test_tmproot.check(dir=1) + + +def test_testdir_makefile_dot_prefixes_extension_silently( + testdir: Testdir, +) -> None: + """For backwards compat #8192""" + p1 = testdir.makefile("foo.bar", "") + assert ".foo.bar" in str(p1) + + +def test_pytester_makefile_dot_prefixes_extension_with_warning( + pytester: Pytester, +) -> None: + with pytest.raises( + ValueError, + match="pytester.makefile expects a file extension, try .foo.bar instead of foo.bar", + ): + pytester.makefile("foo.bar", "") + + +def test_testdir_makefile_ext_none_raises_type_error(testdir) -> None: + """For backwards compat #8192""" + with pytest.raises(TypeError): + testdir.makefile(None, "") + + +def test_testdir_makefile_ext_empty_string_makes_file(testdir) -> None: + """For backwards compat #8192""" + p1 = testdir.makefile("", "") + assert "test_testdir_makefile" in str(p1) From 65b8391ead4ef3849e6181e7ec899625b78ba992 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 5 Jan 2021 19:39:50 +0100 Subject: [PATCH 0061/2772] doc: Add note about training early bird discount --- doc/en/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/index.rst b/doc/en/index.rst index ad2057ff14a..58f6c1d86c7 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -2,7 +2,7 @@ .. sidebar:: Next Open Trainings - - `Professional testing with Python `_, via Python Academy, February 1-3 2021, Leipzig (Germany) and remote. + - `Professional testing with Python `_, via Python Academy, February 1-3 2021, remote and Leipzig (Germany). **Early-bird discount available until January 15th**. Also see `previous talks and blogposts `_. From 78fb97105f38dc286353bbc331a243b6e753fe3c Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 6 Jan 2021 13:33:33 +0100 Subject: [PATCH 0062/2772] Make code.FormattedExcinfo.get_source more defensive When line_index was a large negative number, get_source failed on `source.lines[line_index]`. Use the same dummy Source as with a large positive line_index. --- src/_pytest/_code/code.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index b8521756067..af3bdf0561b 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -721,11 +721,11 @@ def get_source( ) -> List[str]: """Return formatted and marked up source lines.""" lines = [] - if source is None or line_index >= len(source.lines): + if source is not None and line_index < 0: + line_index += len(source.lines) + if source is None or line_index >= len(source.lines) or line_index < 0: source = Source("???") line_index = 0 - if line_index < 0: - line_index += len(source) space_prefix = " " if short: lines.append(space_prefix + source.lines[line_index].strip()) From 80c33c817879ca9cf1eb66e4fa2ff7a01f33bb39 Mon Sep 17 00:00:00 2001 From: Gergely Imreh Date: Fri, 8 Jan 2021 11:43:51 +0000 Subject: [PATCH 0063/2772] Add missing import into example script in documentation --- doc/en/skipping.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/en/skipping.rst b/doc/en/skipping.rst index 282820545c3..610d3d43bca 100644 --- a/doc/en/skipping.rst +++ b/doc/en/skipping.rst @@ -405,6 +405,7 @@ test instances when using parametrize: .. code-block:: python + import sys import pytest From af78efc7fa903bd7f445c451212ab625c11954cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Jan 2021 03:15:59 +0000 Subject: [PATCH 0064/2772] build(deps): bump pytest-mock in /testing/plugins_integration Bumps [pytest-mock](https://github.com/pytest-dev/pytest-mock) from 3.4.0 to 3.5.1. - [Release notes](https://github.com/pytest-dev/pytest-mock/releases) - [Changelog](https://github.com/pytest-dev/pytest-mock/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-mock/compare/v3.4.0...v3.5.1) Signed-off-by: dependabot[bot] --- testing/plugins_integration/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt index b2ca3e3236a..c48a5cfd257 100644 --- a/testing/plugins_integration/requirements.txt +++ b/testing/plugins_integration/requirements.txt @@ -6,7 +6,7 @@ pytest-cov==2.10.1 pytest-django==4.1.0 pytest-flakes==4.0.3 pytest-html==3.1.1 -pytest-mock==3.4.0 +pytest-mock==3.5.1 pytest-rerunfailures==9.1.1 pytest-sugar==0.9.4 pytest-trio==0.7.0 From cfa0c3b0d94eea25db8f7de57ce93c52598009db Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Jan 2021 03:16:03 +0000 Subject: [PATCH 0065/2772] build(deps): bump django in /testing/plugins_integration Bumps [django](https://github.com/django/django) from 3.1.4 to 3.1.5. - [Release notes](https://github.com/django/django/releases) - [Commits](https://github.com/django/django/compare/3.1.4...3.1.5) Signed-off-by: dependabot[bot] --- testing/plugins_integration/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt index b2ca3e3236a..f5036023097 100644 --- a/testing/plugins_integration/requirements.txt +++ b/testing/plugins_integration/requirements.txt @@ -1,5 +1,5 @@ anyio[curio,trio]==2.0.2 -django==3.1.4 +django==3.1.5 pytest-asyncio==0.14.0 pytest-bdd==4.0.2 pytest-cov==2.10.1 From 42d5545f42f7f11345913efedf852cbea3753e58 Mon Sep 17 00:00:00 2001 From: Anton <44246099+antonblr@users.noreply.github.com> Date: Wed, 13 Jan 2021 17:02:26 -0800 Subject: [PATCH 0066/2772] unittest: cleanup unexpected success handling (#8231) * unittest: cleanup unexpected success handling * update comment --- src/_pytest/skipping.py | 11 +---------- src/_pytest/unittest.py | 24 ++++++++++++------------ testing/test_unittest.py | 9 +++++++-- 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/_pytest/skipping.py b/src/_pytest/skipping.py index 9aacfecee7a..c7afef5db87 100644 --- a/src/_pytest/skipping.py +++ b/src/_pytest/skipping.py @@ -234,7 +234,6 @@ def evaluate_xfail_marks(item: Item) -> Optional[Xfail]: skipped_by_mark_key = StoreKey[bool]() # Saves the xfail mark evaluation. Can be refreshed during call if None. xfailed_key = StoreKey[Optional[Xfail]]() -unexpectedsuccess_key = StoreKey[str]() @hookimpl(tryfirst=True) @@ -271,15 +270,7 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]): outcome = yield rep = outcome.get_result() xfailed = item._store.get(xfailed_key, None) - # unittest special case, see setting of unexpectedsuccess_key - if unexpectedsuccess_key in item._store and rep.when == "call": - reason = item._store[unexpectedsuccess_key] - if reason: - rep.longrepr = f"Unexpected success: {reason}" - else: - rep.longrepr = "Unexpected success" - rep.outcome = "failed" - elif item.config.option.runxfail: + if item.config.option.runxfail: pass # don't interfere elif call.excinfo and isinstance(call.excinfo.value, xfail.Exception): assert call.excinfo.value.msg is not None diff --git a/src/_pytest/unittest.py b/src/_pytest/unittest.py index 55f15efe4b7..cc616578b09 100644 --- a/src/_pytest/unittest.py +++ b/src/_pytest/unittest.py @@ -30,10 +30,10 @@ from _pytest.python import PyCollector from _pytest.runner import CallInfo from _pytest.skipping import skipped_by_mark_key -from _pytest.skipping import unexpectedsuccess_key if TYPE_CHECKING: import unittest + import twisted.trial.unittest from _pytest.fixtures import _Scope @@ -273,9 +273,18 @@ def addExpectedFailure( self._addexcinfo(sys.exc_info()) def addUnexpectedSuccess( - self, testcase: "unittest.TestCase", reason: str = "" + self, + testcase: "unittest.TestCase", + reason: Optional["twisted.trial.unittest.Todo"] = None, ) -> None: - self._store[unexpectedsuccess_key] = reason + msg = "Unexpected success" + if reason: + msg += f": {reason.reason}" + # Preserve unittest behaviour - fail the test. Explicitly not an XPASS. + try: + fail(msg, pytrace=False) + except fail.Exception: + self._addexcinfo(sys.exc_info()) def addSuccess(self, testcase: "unittest.TestCase") -> None: pass @@ -283,15 +292,6 @@ def addSuccess(self, testcase: "unittest.TestCase") -> None: def stopTest(self, testcase: "unittest.TestCase") -> None: pass - def _expecting_failure(self, test_method) -> bool: - """Return True if the given unittest method (or the entire class) is marked - with @expectedFailure.""" - expecting_failure_method = getattr( - test_method, "__unittest_expecting_failure__", False - ) - expecting_failure_class = getattr(self, "__unittest_expecting_failure__", False) - return bool(expecting_failure_class or expecting_failure_method) - def runtest(self) -> None: from _pytest.debugging import maybe_wrap_pytest_function_for_tracing diff --git a/testing/test_unittest.py b/testing/test_unittest.py index feee09286c2..69bafc26d61 100644 --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -765,7 +765,8 @@ def test_failing_test_is_xfail(self): @pytest.mark.parametrize("runner", ["pytest", "unittest"]) def test_unittest_expected_failure_for_passing_test_is_fail( - pytester: Pytester, runner + pytester: Pytester, + runner: str, ) -> None: script = pytester.makepyfile( """ @@ -782,7 +783,11 @@ def test_passing_test_is_fail(self): if runner == "pytest": result = pytester.runpytest("-rxX") result.stdout.fnmatch_lines( - ["*MyTestCase*test_passing_test_is_fail*", "*1 failed*"] + [ + "*MyTestCase*test_passing_test_is_fail*", + "Unexpected success", + "*1 failed*", + ] ) else: result = pytester.runpython(script) From addbd3161e37edffebb2c0f6527da49b0515d1a1 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Thu, 14 Jan 2021 16:51:18 +0200 Subject: [PATCH 0067/2772] nose,fixtures: use the public item API for adding finalizers --- src/_pytest/fixtures.py | 10 +++------- src/_pytest/nose.py | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 5bdee3096b1..43a40a86449 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -543,10 +543,8 @@ def addfinalizer(self, finalizer: Callable[[], object]) -> None: self._addfinalizer(finalizer, scope=self.scope) def _addfinalizer(self, finalizer: Callable[[], object], scope) -> None: - colitem = self._getscopeitem(scope) - self._pyfuncitem.session._setupstate.addfinalizer( - finalizer=finalizer, colitem=colitem - ) + item = self._getscopeitem(scope) + item.addfinalizer(finalizer) def applymarker(self, marker: Union[str, MarkDecorator]) -> None: """Apply a marker to a single test function invocation. @@ -694,9 +692,7 @@ def _schedule_finalizers( self, fixturedef: "FixtureDef[object]", subrequest: "SubRequest" ) -> None: # If fixture function failed it might have registered finalizers. - self.session._setupstate.addfinalizer( - functools.partial(fixturedef.finish, request=subrequest), subrequest.node - ) + subrequest.node.addfinalizer(lambda: fixturedef.finish(request=subrequest)) def _check_scope( self, diff --git a/src/_pytest/nose.py b/src/_pytest/nose.py index de91af85af6..5bba030a509 100644 --- a/src/_pytest/nose.py +++ b/src/_pytest/nose.py @@ -13,7 +13,7 @@ def pytest_runtest_setup(item) -> None: # Call module level setup if there is no object level one. call_optional(item.parent.obj, "setup") # XXX This implies we only call teardown when setup worked. - item.session._setupstate.addfinalizer((lambda: teardown_nose(item)), item) + item.addfinalizer(lambda: teardown_nose(item)) def teardown_nose(item) -> None: From 096bae6c68840c19bdc97cdfdf99b96dbcb2c427 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Thu, 14 Jan 2021 17:05:01 +0200 Subject: [PATCH 0068/2772] unittest: add clarifying comment on unittest.SkipTest -> pytest.skip code I was tempted to remove it, until I figured out why it was there. --- src/_pytest/unittest.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/_pytest/unittest.py b/src/_pytest/unittest.py index cc616578b09..6a90188cab9 100644 --- a/src/_pytest/unittest.py +++ b/src/_pytest/unittest.py @@ -343,6 +343,10 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> None: except AttributeError: pass + # Convert unittest.SkipTest to pytest.skip. + # This is actually only needed for nose, which reuses unittest.SkipTest for + # its own nose.SkipTest. For unittest TestCases, SkipTest is already + # handled internally, and doesn't reach here. unittest = sys.modules.get("unittest") if ( unittest @@ -350,7 +354,6 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> None: and isinstance(call.excinfo.value, unittest.SkipTest) # type: ignore[attr-defined] ): excinfo = call.excinfo - # Let's substitute the excinfo with a pytest.skip one. call2 = CallInfo[None].from_call( lambda: pytest.skip(str(excinfo.value)), call.when ) From 3dde519f53b4480a3b30d58a1c71ca4505ae5ff7 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Thu, 14 Jan 2021 17:42:38 +0200 Subject: [PATCH 0069/2772] nose: type annotate with some resulting refactoring --- src/_pytest/nose.py | 48 +++++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/src/_pytest/nose.py b/src/_pytest/nose.py index 5bba030a509..16d5224e9fa 100644 --- a/src/_pytest/nose.py +++ b/src/_pytest/nose.py @@ -1,33 +1,35 @@ """Run testsuites written for nose.""" -from _pytest import python -from _pytest import unittest from _pytest.config import hookimpl from _pytest.fixtures import getfixturemarker from _pytest.nodes import Item +from _pytest.python import Function +from _pytest.unittest import TestCaseFunction @hookimpl(trylast=True) -def pytest_runtest_setup(item) -> None: - if is_potential_nosetest(item): - if not call_optional(item.obj, "setup"): - # Call module level setup if there is no object level one. - call_optional(item.parent.obj, "setup") - # XXX This implies we only call teardown when setup worked. - item.addfinalizer(lambda: teardown_nose(item)) - - -def teardown_nose(item) -> None: - if is_potential_nosetest(item): - if not call_optional(item.obj, "teardown"): - call_optional(item.parent.obj, "teardown") - - -def is_potential_nosetest(item: Item) -> bool: - # Extra check needed since we do not do nose style setup/teardown - # on direct unittest style classes. - return isinstance(item, python.Function) and not isinstance( - item, unittest.TestCaseFunction - ) +def pytest_runtest_setup(item: Item) -> None: + if not isinstance(item, Function): + return + # Don't do nose style setup/teardown on direct unittest style classes. + if isinstance(item, TestCaseFunction): + return + + # Capture the narrowed type of item for the teardown closure, + # see https://github.com/python/mypy/issues/2608 + func = item + + if not call_optional(func.obj, "setup"): + # Call module level setup if there is no object level one. + assert func.parent is not None + call_optional(func.parent.obj, "setup") # type: ignore[attr-defined] + + def teardown_nose() -> None: + if not call_optional(func.obj, "teardown"): + assert func.parent is not None + call_optional(func.parent.obj, "teardown") # type: ignore[attr-defined] + + # XXX This implies we only call teardown when setup worked. + func.addfinalizer(teardown_nose) def call_optional(obj: object, name: str) -> bool: From 7f989203ed58119bf63e026cbb99df274c7700d6 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Thu, 14 Jan 2021 11:58:59 +0200 Subject: [PATCH 0070/2772] Improve way in which skip location is fixed up when skipped by mark When `pytest.skip()` is called inside a test function, the skip location should be reported as the line that made the call, however when `pytest.skip()` is called by the `pytest.mark.skip` and similar mechanisms, the location should be reported at the item's location, because the exact location is some irrelevant internal code. Currently the item-location case is implemented by the caller setting a boolean key on the item's store and the `skipping` plugin checking it and fixing up the location if needed. This is really roundabout IMO and breaks encapsulation. Instead, allow the caller to specify directly on the skip exception whether to use the item's location or not. For now, this is entirely private. --- src/_pytest/outcomes.py | 5 +++++ src/_pytest/reports.py | 7 ++++++- src/_pytest/skipping.py | 18 +----------------- src/_pytest/unittest.py | 6 ++---- 4 files changed, 14 insertions(+), 22 deletions(-) diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py index 8f6203fd7fa..756b4098b36 100644 --- a/src/_pytest/outcomes.py +++ b/src/_pytest/outcomes.py @@ -58,9 +58,14 @@ def __init__( msg: Optional[str] = None, pytrace: bool = True, allow_module_level: bool = False, + *, + _use_item_location: bool = False, ) -> None: OutcomeException.__init__(self, msg=msg, pytrace=pytrace) self.allow_module_level = allow_module_level + # If true, the skip location is reported as the item's location, + # instead of the place that raises the exception/calls skip(). + self._use_item_location = _use_item_location class Failed(OutcomeException): diff --git a/src/_pytest/reports.py b/src/_pytest/reports.py index d2d7115b2e5..303f731ddaa 100644 --- a/src/_pytest/reports.py +++ b/src/_pytest/reports.py @@ -324,7 +324,12 @@ def from_item_and_call(cls, item: Item, call: "CallInfo[None]") -> "TestReport": elif isinstance(excinfo.value, skip.Exception): outcome = "skipped" r = excinfo._getreprcrash() - longrepr = (str(r.path), r.lineno, r.message) + if excinfo.value._use_item_location: + filename, line = item.reportinfo()[:2] + assert line is not None + longrepr = str(filename), line + 1, r.message + else: + longrepr = (str(r.path), r.lineno, r.message) else: outcome = "failed" if call.when == "call": diff --git a/src/_pytest/skipping.py b/src/_pytest/skipping.py index c7afef5db87..1ad312919ca 100644 --- a/src/_pytest/skipping.py +++ b/src/_pytest/skipping.py @@ -230,8 +230,6 @@ def evaluate_xfail_marks(item: Item) -> Optional[Xfail]: return None -# Whether skipped due to skip or skipif marks. -skipped_by_mark_key = StoreKey[bool]() # Saves the xfail mark evaluation. Can be refreshed during call if None. xfailed_key = StoreKey[Optional[Xfail]]() @@ -239,9 +237,8 @@ def evaluate_xfail_marks(item: Item) -> Optional[Xfail]: @hookimpl(tryfirst=True) def pytest_runtest_setup(item: Item) -> None: skipped = evaluate_skip_marks(item) - item._store[skipped_by_mark_key] = skipped is not None if skipped: - skip(skipped.reason) + raise skip.Exception(skipped.reason, _use_item_location=True) item._store[xfailed_key] = xfailed = evaluate_xfail_marks(item) if xfailed and not item.config.option.runxfail and not xfailed.run: @@ -292,19 +289,6 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]): rep.outcome = "passed" rep.wasxfail = xfailed.reason - if ( - item._store.get(skipped_by_mark_key, True) - and rep.skipped - and type(rep.longrepr) is tuple - ): - # Skipped by mark.skipif; change the location of the failure - # to point to the item definition, otherwise it will display - # the location of where the skip exception was raised within pytest. - _, _, reason = rep.longrepr - filename, line = item.reportinfo()[:2] - assert line is not None - rep.longrepr = str(filename), line + 1, reason - def pytest_report_teststatus(report: BaseReport) -> Optional[Tuple[str, str, str]]: if hasattr(report, "wasxfail"): diff --git a/src/_pytest/unittest.py b/src/_pytest/unittest.py index 6a90188cab9..719eb4e8823 100644 --- a/src/_pytest/unittest.py +++ b/src/_pytest/unittest.py @@ -29,7 +29,6 @@ from _pytest.python import Function from _pytest.python import PyCollector from _pytest.runner import CallInfo -from _pytest.skipping import skipped_by_mark_key if TYPE_CHECKING: import unittest @@ -150,7 +149,7 @@ def cleanup(*args): def fixture(self, request: FixtureRequest) -> Generator[None, None, None]: if _is_skipped(self): reason = self.__unittest_skip_why__ - pytest.skip(reason) + raise pytest.skip.Exception(reason, _use_item_location=True) if setup is not None: try: if pass_self: @@ -256,9 +255,8 @@ def addFailure( def addSkip(self, testcase: "unittest.TestCase", reason: str) -> None: try: - skip(reason) + raise pytest.skip.Exception(reason, _use_item_location=True) except skip.Exception: - self._store[skipped_by_mark_key] = True self._addexcinfo(sys.exc_info()) def addExpectedFailure( From 25e657bfc15aecd8fa8787a028ffc10fc9ac96d5 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Thu, 14 Jan 2021 18:14:39 +0200 Subject: [PATCH 0071/2772] Deprecate raising unittest.SkipTest to skip tests during collection It is not very clear why this code exists -- we are not running any unittest or nose code during collection, and really these frameworks don't have the concept of collection at all, and just raising these exceptions at e.g. the module level would cause an error. So unless I'm missing something, I don't think anyone is using this. Deprecate it so we can eventually clear up this code and keep unittest more tightly restricted to its plugin. --- changelog/8242.deprecation.rst | 7 +++++++ doc/en/deprecations.rst | 14 ++++++++++++++ src/_pytest/deprecated.py | 5 +++++ src/_pytest/runner.py | 7 +++++++ testing/deprecated_test.py | 17 +++++++++++++++++ 5 files changed, 50 insertions(+) create mode 100644 changelog/8242.deprecation.rst diff --git a/changelog/8242.deprecation.rst b/changelog/8242.deprecation.rst new file mode 100644 index 00000000000..b2e8566eaa9 --- /dev/null +++ b/changelog/8242.deprecation.rst @@ -0,0 +1,7 @@ +Raising :class:`unittest.SkipTest` to skip collection of tests during the +pytest collection phase is deprecated. Use :func:`pytest.skip` instead. + +Note: This deprecation only relates to using `unittest.SkipTest` during test +collection. You are probably not doing that. Ordinary usage of +:class:`unittest.SkipTest` / :meth:`unittest.TestCase.skipTest` / +:func:`unittest.skip` in unittest test cases is fully supported. diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index ec2397e596f..0dcbd8ceb36 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -18,6 +18,20 @@ Deprecated Features Below is a complete list of all pytest features which are considered deprecated. Using those features will issue :class:`PytestWarning` or subclasses, which can be filtered using :ref:`standard warning filters `. +Raising ``unittest.SkipTest`` during collection +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 6.3 + +Raising :class:`unittest.SkipTest` to skip collection of tests during the +pytest collection phase is deprecated. Use :func:`pytest.skip` instead. + +Note: This deprecation only relates to using `unittest.SkipTest` during test +collection. You are probably not doing that. Ordinary usage of +:class:`unittest.SkipTest` / :meth:`unittest.TestCase.skipTest` / +:func:`unittest.skip` in unittest test cases is fully supported. + + The ``--strict`` command-line option ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 19b31d66538..fa91f909769 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -64,6 +64,11 @@ PRIVATE = PytestDeprecationWarning("A private pytest class or function was used.") +UNITTEST_SKIP_DURING_COLLECTION = PytestDeprecationWarning( + "Raising unittest.SkipTest to skip tests during collection is deprecated. " + "Use pytest.skip() instead." +) + # You want to make some `__init__` or function "private". # diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index df046a78aca..844e41f8057 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -2,6 +2,7 @@ import bdb import os import sys +import warnings from typing import Callable from typing import cast from typing import Dict @@ -27,6 +28,7 @@ from _pytest.compat import final from _pytest.config.argparsing import Parser from _pytest.deprecated import check_ispytest +from _pytest.deprecated import UNITTEST_SKIP_DURING_COLLECTION from _pytest.nodes import Collector from _pytest.nodes import Item from _pytest.nodes import Node @@ -374,6 +376,11 @@ def pytest_make_collect_report(collector: Collector) -> CollectReport: # Type ignored because unittest is loaded dynamically. skip_exceptions.append(unittest.SkipTest) # type: ignore if isinstance(call.excinfo.value, tuple(skip_exceptions)): + if unittest is not None and isinstance( + call.excinfo.value, unittest.SkipTest # type: ignore[attr-defined] + ): + warnings.warn(UNITTEST_SKIP_DURING_COLLECTION, stacklevel=2) + outcome = "skipped" r_ = collector._repr_failure_py(call.excinfo, "line") assert isinstance(r_, ExceptionChainRepr), repr(r_) diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index 6d92d181f99..18300f62a1a 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -136,3 +136,20 @@ def __init__(self, foo: int, *, _ispytest: bool = False) -> None: # Doesn't warn. PrivateInit(10, _ispytest=True) + + +def test_raising_unittest_skiptest_during_collection_is_deprecated( + pytester: Pytester, +) -> None: + pytester.makepyfile( + """ + import unittest + raise unittest.SkipTest() + """ + ) + result = pytester.runpytest() + result.stdout.fnmatch_lines( + [ + "*PytestDeprecationWarning: Raising unittest.SkipTest*", + ] + ) From a9e43152bc5081afccc6de6ef5526ff35c525fed Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Sun, 17 Jan 2021 14:23:07 +0100 Subject: [PATCH 0072/2772] alter the PyObjMixin to carry over typing information from Node as PyObjMixin is always supposed to be mixed in the mro before nodes.Node the behavior doesn't change, but all the typing information carry over to help mypy. extracted from #8037 --- changelog/8248.trivial.rst | 1 + src/_pytest/python.py | 18 +++++------------- 2 files changed, 6 insertions(+), 13 deletions(-) create mode 100644 changelog/8248.trivial.rst diff --git a/changelog/8248.trivial.rst b/changelog/8248.trivial.rst new file mode 100644 index 00000000000..0a9319d9cd5 --- /dev/null +++ b/changelog/8248.trivial.rst @@ -0,0 +1 @@ +Internal Restructure: let python.PyObjMixing inherit from nodes.Node to carry over typing information. diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 29ebd176bbb..eabd7b31154 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -23,7 +23,6 @@ from typing import Sequence from typing import Set from typing import Tuple -from typing import Type from typing import TYPE_CHECKING from typing import Union @@ -255,20 +254,13 @@ def pytest_pycollect_makeitem(collector: "PyCollector", name: str, obj: object): return res -class PyobjMixin: - _ALLOW_MARKERS = True - - # Function and attributes that the mixin needs (for type-checking only). - if TYPE_CHECKING: - name: str = "" - parent: Optional[nodes.Node] = None - own_markers: List[Mark] = [] +class PyobjMixin(nodes.Node): + """this mix-in inherits from Node to carry over the typing information - def getparent(self, cls: Type[nodes._NodeType]) -> Optional[nodes._NodeType]: - ... + as its intended to always mix in before a node + its position in the mro is unaffected""" - def listchain(self) -> List[nodes.Node]: - ... + _ALLOW_MARKERS = True @property def module(self): From 9ba1821e9121c3ee49a68b4863cd7ee50de4a5a5 Mon Sep 17 00:00:00 2001 From: Andreas Motl Date: Sun, 17 Jan 2021 19:23:57 +0100 Subject: [PATCH 0073/2772] Fix faulthandler for Twisted Logger when used with "--capture=no" The Twisted Logger will return an invalid file descriptor since it is not backed by an FD. So, let's also forward this to the same code path as with `pytest-xdist`. --- AUTHORS | 1 + changelog/8249.bugfix.rst | 1 + src/_pytest/faulthandler.py | 7 ++++++- testing/test_faulthandler.py | 25 +++++++++++++++++++++++++ 4 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 changelog/8249.bugfix.rst diff --git a/AUTHORS b/AUTHORS index e75baa8cd92..0c721a49610 100644 --- a/AUTHORS +++ b/AUTHORS @@ -21,6 +21,7 @@ Anders Hovmöller Andras Mitzki Andras Tim Andrea Cimatoribus +Andreas Motl Andreas Zeidler Andrey Paramonov Andrzej Klajnert diff --git a/changelog/8249.bugfix.rst b/changelog/8249.bugfix.rst new file mode 100644 index 00000000000..aa084c75738 --- /dev/null +++ b/changelog/8249.bugfix.rst @@ -0,0 +1 @@ +Fix the ``faulthandler`` plugin for occasions when running with ``twisted.logger`` and using ``pytest --capture=no``. diff --git a/src/_pytest/faulthandler.py b/src/_pytest/faulthandler.py index d0cc0430c49..ff673b5b164 100644 --- a/src/_pytest/faulthandler.py +++ b/src/_pytest/faulthandler.py @@ -69,7 +69,12 @@ def pytest_unconfigure(self, config: Config) -> None: @staticmethod def _get_stderr_fileno(): try: - return sys.stderr.fileno() + fileno = sys.stderr.fileno() + # The Twisted Logger will return an invalid file descriptor since it is not backed + # by an FD. So, let's also forward this to the same code path as with pytest-xdist. + if fileno == -1: + raise AttributeError() + return fileno except (AttributeError, io.UnsupportedOperation): # pytest-xdist monkeypatches sys.stderr with an object that is not an actual file. # https://docs.python.org/3/library/faulthandler.html#issue-with-file-descriptors diff --git a/testing/test_faulthandler.py b/testing/test_faulthandler.py index caf39813cf4..370084c125f 100644 --- a/testing/test_faulthandler.py +++ b/testing/test_faulthandler.py @@ -1,3 +1,4 @@ +import io import sys import pytest @@ -135,3 +136,27 @@ def test(): result.stdout.no_fnmatch_line(warning_line) result.stdout.fnmatch_lines("*1 passed*") assert result.ret == 0 + + +def test_get_stderr_fileno_invalid_fd() -> None: + """Test for faulthandler being able to handle invalid file descriptors for stderr (#8249).""" + from _pytest.faulthandler import FaultHandlerHooks + + class StdErrWrapper(io.StringIO): + """ + Mimic ``twisted.logger.LoggingFile`` to simulate returning an invalid file descriptor. + + https://github.com/twisted/twisted/blob/twisted-20.3.0/src/twisted/logger/_io.py#L132-L139 + """ + + def fileno(self): + return -1 + + wrapper = StdErrWrapper() + + with pytest.MonkeyPatch.context() as mp: + mp.setattr("sys.stderr", wrapper) + + # Even when the stderr wrapper signals an invalid file descriptor, + # ``_get_stderr_fileno()`` should return the real one. + assert FaultHandlerHooks._get_stderr_fileno() == 2 From eef2d1a8e268ef727eec8f1c38119ee76cdaebc2 Mon Sep 17 00:00:00 2001 From: Jeff Widman Date: Tue, 19 Jan 2021 06:45:45 -0800 Subject: [PATCH 0074/2772] Fix pep8 import order in docs (#8253) --- doc/en/example/parametrize.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index 6e2f53984ee..a65ee5f2fd9 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -97,10 +97,10 @@ the argument name: # content of test_time.py - import pytest - from datetime import datetime, timedelta + import pytest + testdata = [ (datetime(2001, 12, 12), datetime(2001, 12, 11), timedelta(1)), (datetime(2001, 12, 11), datetime(2001, 12, 12), timedelta(-1)), From adc0f29b8f8fa8a63e0592c38503855a5b615a29 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 20 Jan 2021 10:05:36 -0300 Subject: [PATCH 0075/2772] Always handle faulthandler stderr even if already enabled It seems the code that would not install pytest's faulthandler support if it was already enabled is not really needed at all, and even detrimental when using `python -X dev -m pytest` to run Python in "dev" mode. Also simplified the plugin by removing the hook class, now the hooks will always be active so there's no need to delay the hook definitions anymore. Fix #8258 --- changelog/8258.bugfix.rst | 3 + src/_pytest/faulthandler.py | 134 +++++++++++++++-------------------- testing/test_faulthandler.py | 29 +++----- 3 files changed, 70 insertions(+), 96 deletions(-) create mode 100644 changelog/8258.bugfix.rst diff --git a/changelog/8258.bugfix.rst b/changelog/8258.bugfix.rst new file mode 100644 index 00000000000..6518ec0b738 --- /dev/null +++ b/changelog/8258.bugfix.rst @@ -0,0 +1,3 @@ +Fixed issue where pytest's ``faulthandler`` support would not dump traceback on crashes +if the :mod:`faulthandler` module was already enabled during pytest startup (using +``python -X dev -m pytest`` for example). diff --git a/src/_pytest/faulthandler.py b/src/_pytest/faulthandler.py index ff673b5b164..9592de82d6d 100644 --- a/src/_pytest/faulthandler.py +++ b/src/_pytest/faulthandler.py @@ -25,92 +25,72 @@ def pytest_addoption(parser: Parser) -> None: def pytest_configure(config: Config) -> None: import faulthandler - if not faulthandler.is_enabled(): - # faulthhandler is not enabled, so install plugin that does the actual work - # of enabling faulthandler before each test executes. - config.pluginmanager.register(FaultHandlerHooks(), "faulthandler-hooks") - else: - # Do not handle dumping to stderr if faulthandler is already enabled, so warn - # users that the option is being ignored. - timeout = FaultHandlerHooks.get_timeout_config_value(config) - if timeout > 0: - config.issue_config_time_warning( - pytest.PytestConfigWarning( - "faulthandler module enabled before pytest configuration step, " - "'faulthandler_timeout' option ignored" - ), - stacklevel=2, - ) - - -class FaultHandlerHooks: - """Implements hooks that will actually install fault handler before tests execute, - as well as correctly handle pdb and internal errors.""" - - def pytest_configure(self, config: Config) -> None: - import faulthandler + stderr_fd_copy = os.dup(get_stderr_fileno()) + config._store[fault_handler_stderr_key] = open(stderr_fd_copy, "w") + faulthandler.enable(file=config._store[fault_handler_stderr_key]) - stderr_fd_copy = os.dup(self._get_stderr_fileno()) - config._store[fault_handler_stderr_key] = open(stderr_fd_copy, "w") - faulthandler.enable(file=config._store[fault_handler_stderr_key]) - def pytest_unconfigure(self, config: Config) -> None: - import faulthandler +def pytest_unconfigure(config: Config) -> None: + import faulthandler - faulthandler.disable() - # close our dup file installed during pytest_configure - # re-enable the faulthandler, attaching it to the default sys.stderr - # so we can see crashes after pytest has finished, usually during - # garbage collection during interpreter shutdown + faulthandler.disable() + # Close the dup file installed during pytest_configure. + if fault_handler_stderr_key in config._store: config._store[fault_handler_stderr_key].close() del config._store[fault_handler_stderr_key] - faulthandler.enable(file=self._get_stderr_fileno()) + # Re-enable the faulthandler, attaching it to the default sys.stderr + # so we can see crashes after pytest has finished, usually during + # garbage collection during interpreter shutdown. + faulthandler.enable(file=get_stderr_fileno()) + + +def get_stderr_fileno() -> int: + try: + fileno = sys.stderr.fileno() + # The Twisted Logger will return an invalid file descriptor since it is not backed + # by an FD. So, let's also forward this to the same code path as with pytest-xdist. + if fileno == -1: + raise AttributeError() + return fileno + except (AttributeError, io.UnsupportedOperation): + # pytest-xdist monkeypatches sys.stderr with an object that is not an actual file. + # https://docs.python.org/3/library/faulthandler.html#issue-with-file-descriptors + # This is potentially dangerous, but the best we can do. + return sys.__stderr__.fileno() + + +def get_timeout_config_value(config: Config) -> float: + return float(config.getini("faulthandler_timeout") or 0.0) + + +@pytest.hookimpl(hookwrapper=True, trylast=True) +def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]: + timeout = get_timeout_config_value(item.config) + stderr = item.config._store[fault_handler_stderr_key] + if timeout > 0 and stderr is not None: + import faulthandler - @staticmethod - def _get_stderr_fileno(): + faulthandler.dump_traceback_later(timeout, file=stderr) try: - fileno = sys.stderr.fileno() - # The Twisted Logger will return an invalid file descriptor since it is not backed - # by an FD. So, let's also forward this to the same code path as with pytest-xdist. - if fileno == -1: - raise AttributeError() - return fileno - except (AttributeError, io.UnsupportedOperation): - # pytest-xdist monkeypatches sys.stderr with an object that is not an actual file. - # https://docs.python.org/3/library/faulthandler.html#issue-with-file-descriptors - # This is potentially dangerous, but the best we can do. - return sys.__stderr__.fileno() - - @staticmethod - def get_timeout_config_value(config): - return float(config.getini("faulthandler_timeout") or 0.0) - - @pytest.hookimpl(hookwrapper=True, trylast=True) - def pytest_runtest_protocol(self, item: Item) -> Generator[None, None, None]: - timeout = self.get_timeout_config_value(item.config) - stderr = item.config._store[fault_handler_stderr_key] - if timeout > 0 and stderr is not None: - import faulthandler - - faulthandler.dump_traceback_later(timeout, file=stderr) - try: - yield - finally: - faulthandler.cancel_dump_traceback_later() - else: yield + finally: + faulthandler.cancel_dump_traceback_later() + else: + yield - @pytest.hookimpl(tryfirst=True) - def pytest_enter_pdb(self) -> None: - """Cancel any traceback dumping due to timeout before entering pdb.""" - import faulthandler - faulthandler.cancel_dump_traceback_later() +@pytest.hookimpl(tryfirst=True) +def pytest_enter_pdb() -> None: + """Cancel any traceback dumping due to timeout before entering pdb.""" + import faulthandler - @pytest.hookimpl(tryfirst=True) - def pytest_exception_interact(self) -> None: - """Cancel any traceback dumping due to an interactive exception being - raised.""" - import faulthandler + faulthandler.cancel_dump_traceback_later() + + +@pytest.hookimpl(tryfirst=True) +def pytest_exception_interact() -> None: + """Cancel any traceback dumping due to an interactive exception being + raised.""" + import faulthandler - faulthandler.cancel_dump_traceback_later() + faulthandler.cancel_dump_traceback_later() diff --git a/testing/test_faulthandler.py b/testing/test_faulthandler.py index 370084c125f..411e841a31a 100644 --- a/testing/test_faulthandler.py +++ b/testing/test_faulthandler.py @@ -94,7 +94,7 @@ def test_cancel_timeout_on_hook(monkeypatch, hook_name) -> None: to timeout before entering pdb (pytest-dev/pytest-faulthandler#12) or any other interactive exception (pytest-dev/pytest-faulthandler#14).""" import faulthandler - from _pytest.faulthandler import FaultHandlerHooks + from _pytest import faulthandler as faulthandler_plugin called = [] @@ -104,19 +104,18 @@ def test_cancel_timeout_on_hook(monkeypatch, hook_name) -> None: # call our hook explicitly, we can trust that pytest will call the hook # for us at the appropriate moment - hook_func = getattr(FaultHandlerHooks, hook_name) - hook_func(self=None) + hook_func = getattr(faulthandler_plugin, hook_name) + hook_func() assert called == [1] -@pytest.mark.parametrize("faulthandler_timeout", [0, 2]) -def test_already_initialized(faulthandler_timeout: int, pytester: Pytester) -> None: - """Test for faulthandler being initialized earlier than pytest (#6575).""" +def test_already_initialized_crash(pytester: Pytester) -> None: + """Even if faulthandler is already initialized, we still dump tracebacks on crashes (#8258).""" pytester.makepyfile( """ def test(): import faulthandler - assert faulthandler.is_enabled() + faulthandler._sigabrt() """ ) result = pytester.run( @@ -125,22 +124,14 @@ def test(): "faulthandler", "-mpytest", pytester.path, - "-o", - f"faulthandler_timeout={faulthandler_timeout}", ) - # ensure warning is emitted if faulthandler_timeout is configured - warning_line = "*faulthandler.py*faulthandler module enabled before*" - if faulthandler_timeout > 0: - result.stdout.fnmatch_lines(warning_line) - else: - result.stdout.no_fnmatch_line(warning_line) - result.stdout.fnmatch_lines("*1 passed*") - assert result.ret == 0 + result.stderr.fnmatch_lines(["*Fatal Python error*"]) + assert result.ret != 0 def test_get_stderr_fileno_invalid_fd() -> None: """Test for faulthandler being able to handle invalid file descriptors for stderr (#8249).""" - from _pytest.faulthandler import FaultHandlerHooks + from _pytest.faulthandler import get_stderr_fileno class StdErrWrapper(io.StringIO): """ @@ -159,4 +150,4 @@ def fileno(self): # Even when the stderr wrapper signals an invalid file descriptor, # ``_get_stderr_fileno()`` should return the real one. - assert FaultHandlerHooks._get_stderr_fileno() == 2 + assert get_stderr_fileno() == 2 From d4f8e4b40ce481df0f34b2eac5d61aefae4ed2e1 Mon Sep 17 00:00:00 2001 From: Hong Xu Date: Thu, 21 Jan 2021 04:58:52 -0800 Subject: [PATCH 0076/2772] Explain how to create binary files in the doc of `pytest.makefile`. (#8255) --- src/_pytest/pytester.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 95b22b3b23e..8ca21d1c538 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -777,7 +777,7 @@ def to_text(s: Union[Any, bytes]) -> str: return ret def makefile(self, ext: str, *args: str, **kwargs: str) -> Path: - r"""Create new file(s) in the test directory. + r"""Create new text file(s) in the test directory. :param str ext: The extension the file(s) should use, including the dot, e.g. `.py`. @@ -797,6 +797,12 @@ def makefile(self, ext: str, *args: str, **kwargs: str) -> Path: pytester.makefile(".ini", pytest="[pytest]\naddopts=-rs\n") + To create binary files, use :meth:`pathlib.Path.write_bytes` directly: + + .. code-block:: python + + filename = pytester.path.joinpath("foo.bin") + filename.write_bytes(b"...") """ return self._makefile(ext, args, kwargs) From da70f61f67cc37209a5d97bca18303bdd7bccbc8 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 30 Dec 2020 16:02:16 +0200 Subject: [PATCH 0077/2772] runner: complete type annotations of SetupState --- src/_pytest/runner.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 844e41f8057..a49879432a2 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -403,22 +403,22 @@ def pytest_make_collect_report(collector: Collector) -> CollectReport: class SetupState: """Shared state for setting up/tearing down test items or collectors.""" - def __init__(self): + def __init__(self) -> None: self.stack: List[Node] = [] self._finalizers: Dict[Node, List[Callable[[], object]]] = {} - def addfinalizer(self, finalizer: Callable[[], object], colitem) -> None: + def addfinalizer(self, finalizer: Callable[[], object], colitem: Node) -> None: """Attach a finalizer to the given colitem.""" assert colitem and not isinstance(colitem, tuple) assert callable(finalizer) # assert colitem in self.stack # some unit tests don't setup stack :/ self._finalizers.setdefault(colitem, []).append(finalizer) - def _pop_and_teardown(self): + def _pop_and_teardown(self) -> None: colitem = self.stack.pop() self._teardown_with_finalization(colitem) - def _callfinalizers(self, colitem) -> None: + def _callfinalizers(self, colitem: Node) -> None: finalizers = self._finalizers.pop(colitem, None) exc = None while finalizers: @@ -433,7 +433,7 @@ def _callfinalizers(self, colitem) -> None: if exc: raise exc - def _teardown_with_finalization(self, colitem) -> None: + def _teardown_with_finalization(self, colitem: Node) -> None: self._callfinalizers(colitem) colitem.teardown() for colitem in self._finalizers: @@ -446,11 +446,11 @@ def teardown_all(self) -> None: self._teardown_with_finalization(key) assert not self._finalizers - def teardown_exact(self, item, nextitem) -> None: + def teardown_exact(self, item: Item, nextitem: Optional[Item]) -> None: needed_collectors = nextitem and nextitem.listchain() or [] self._teardown_towards(needed_collectors) - def _teardown_towards(self, needed_collectors) -> None: + def _teardown_towards(self, needed_collectors: List[Node]) -> None: exc = None while self.stack: if self.stack == needed_collectors[: len(self.stack)]: @@ -465,7 +465,7 @@ def _teardown_towards(self, needed_collectors) -> None: if exc: raise exc - def prepare(self, colitem) -> None: + def prepare(self, colitem: Item) -> None: """Setup objects along the collector chain to the test-method.""" # Check if the last collection node has raised an error. From f7b0b1dd1f6f2722da2cc1a908fdd2843cbdb111 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 30 Dec 2020 17:20:19 +0200 Subject: [PATCH 0078/2772] runner: use node's Store to keep private SetupState state instead of an attribute This way it gets proper typing and decoupling. --- src/_pytest/runner.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index a49879432a2..7087c6c590a 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -33,8 +33,10 @@ from _pytest.nodes import Item from _pytest.nodes import Node from _pytest.outcomes import Exit +from _pytest.outcomes import OutcomeException from _pytest.outcomes import Skipped from _pytest.outcomes import TEST_OUTCOME +from _pytest.store import StoreKey if TYPE_CHECKING: from typing_extensions import Literal @@ -465,14 +467,16 @@ def _teardown_towards(self, needed_collectors: List[Node]) -> None: if exc: raise exc + _prepare_exc_key = StoreKey[Union[OutcomeException, Exception]]() + def prepare(self, colitem: Item) -> None: """Setup objects along the collector chain to the test-method.""" # Check if the last collection node has raised an error. for col in self.stack: - if hasattr(col, "_prepare_exc"): - exc = col._prepare_exc # type: ignore[attr-defined] - raise exc + prepare_exc = col._store.get(self._prepare_exc_key, None) + if prepare_exc: + raise prepare_exc needed_collectors = colitem.listchain() for col in needed_collectors[len(self.stack) :]: @@ -480,7 +484,7 @@ def prepare(self, colitem: Item) -> None: try: col.setup() except TEST_OUTCOME as e: - col._prepare_exc = e # type: ignore[attr-defined] + col._store[self._prepare_exc_key] = e raise e From 410622f719fd0175cf875f528f122788715b1f05 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 30 Dec 2020 17:27:43 +0200 Subject: [PATCH 0079/2772] runner: reorder SetupState method to make more sense The setup stuff happens before the teardown stuff, so put it first so that reading the code from top to bottom makes more sense. --- src/_pytest/runner.py | 52 +++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 7087c6c590a..d46dba56f5a 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -409,6 +409,26 @@ def __init__(self) -> None: self.stack: List[Node] = [] self._finalizers: Dict[Node, List[Callable[[], object]]] = {} + _prepare_exc_key = StoreKey[Union[OutcomeException, Exception]]() + + def prepare(self, colitem: Item) -> None: + """Setup objects along the collector chain to the test-method.""" + + # Check if the last collection node has raised an error. + for col in self.stack: + prepare_exc = col._store.get(self._prepare_exc_key, None) + if prepare_exc: + raise prepare_exc + + needed_collectors = colitem.listchain() + for col in needed_collectors[len(self.stack) :]: + self.stack.append(col) + try: + col.setup() + except TEST_OUTCOME as e: + col._store[self._prepare_exc_key] = e + raise e + def addfinalizer(self, finalizer: Callable[[], object], colitem: Node) -> None: """Attach a finalizer to the given colitem.""" assert colitem and not isinstance(colitem, tuple) @@ -441,13 +461,6 @@ def _teardown_with_finalization(self, colitem: Node) -> None: for colitem in self._finalizers: assert colitem in self.stack - def teardown_all(self) -> None: - while self.stack: - self._pop_and_teardown() - for key in list(self._finalizers): - self._teardown_with_finalization(key) - assert not self._finalizers - def teardown_exact(self, item: Item, nextitem: Optional[Item]) -> None: needed_collectors = nextitem and nextitem.listchain() or [] self._teardown_towards(needed_collectors) @@ -467,25 +480,12 @@ def _teardown_towards(self, needed_collectors: List[Node]) -> None: if exc: raise exc - _prepare_exc_key = StoreKey[Union[OutcomeException, Exception]]() - - def prepare(self, colitem: Item) -> None: - """Setup objects along the collector chain to the test-method.""" - - # Check if the last collection node has raised an error. - for col in self.stack: - prepare_exc = col._store.get(self._prepare_exc_key, None) - if prepare_exc: - raise prepare_exc - - needed_collectors = colitem.listchain() - for col in needed_collectors[len(self.stack) :]: - self.stack.append(col) - try: - col.setup() - except TEST_OUTCOME as e: - col._store[self._prepare_exc_key] = e - raise e + def teardown_all(self) -> None: + while self.stack: + self._pop_and_teardown() + for key in list(self._finalizers): + self._teardown_with_finalization(key) + assert not self._finalizers def collect_one_node(collector: Collector) -> CollectReport: From 42ae8180ddf36fe4810bdfcce72aa72cf8cd3b43 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 30 Dec 2020 17:33:09 +0200 Subject: [PATCH 0080/2772] runner: inline SetupState._teardown_towards() Doesn't add much. --- src/_pytest/runner.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index d46dba56f5a..e132a2d8f4a 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -463,9 +463,6 @@ def _teardown_with_finalization(self, colitem: Node) -> None: def teardown_exact(self, item: Item, nextitem: Optional[Item]) -> None: needed_collectors = nextitem and nextitem.listchain() or [] - self._teardown_towards(needed_collectors) - - def _teardown_towards(self, needed_collectors: List[Node]) -> None: exc = None while self.stack: if self.stack == needed_collectors[: len(self.stack)]: From 5f4e55fb6d01a1dd199ed0f484e460be7d0e222a Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 30 Dec 2020 17:34:00 +0200 Subject: [PATCH 0081/2772] runner: remove dead code in teardown_all() When the stack is empty, the finalizers which are supposed to be attached to nodes in the stack really ought to be empty as well. So the code here is dead. If this doesn't happen, the assert will trigger. --- src/_pytest/runner.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index e132a2d8f4a..017573cec9f 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -480,8 +480,6 @@ def teardown_exact(self, item: Item, nextitem: Optional[Item]) -> None: def teardown_all(self) -> None: while self.stack: self._pop_and_teardown() - for key in list(self._finalizers): - self._teardown_with_finalization(key) assert not self._finalizers From ceb4d6f6d595532be599e6382b571f3b4f614df9 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 30 Dec 2020 17:36:42 +0200 Subject: [PATCH 0082/2772] runner: inline a couple of SetupState methods Code is clearer this way. --- src/_pytest/runner.py | 6 ------ testing/test_runner.py | 6 ++++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 017573cec9f..5d35f520ac6 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -438,9 +438,6 @@ def addfinalizer(self, finalizer: Callable[[], object], colitem: Node) -> None: def _pop_and_teardown(self) -> None: colitem = self.stack.pop() - self._teardown_with_finalization(colitem) - - def _callfinalizers(self, colitem: Node) -> None: finalizers = self._finalizers.pop(colitem, None) exc = None while finalizers: @@ -454,9 +451,6 @@ def _callfinalizers(self, colitem: Node) -> None: exc = e if exc: raise exc - - def _teardown_with_finalization(self, colitem: Node) -> None: - self._callfinalizers(colitem) colitem.teardown() for colitem in self._finalizers: assert colitem in self.stack diff --git a/testing/test_runner.py b/testing/test_runner.py index 8ce0f67354f..20c81a62f22 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -64,11 +64,12 @@ def fin3(): item = pytester.getitem("def test_func(): pass") ss = runner.SetupState() + ss.prepare(item) ss.addfinalizer(fin1, item) ss.addfinalizer(fin2, item) ss.addfinalizer(fin3, item) with pytest.raises(Exception) as err: - ss._callfinalizers(item) + ss.teardown_exact(item, None) assert err.value.args == ("oops",) assert r == ["fin3", "fin1"] @@ -83,10 +84,11 @@ def fin2(): item = pytester.getitem("def test_func(): pass") ss = runner.SetupState() + ss.prepare(item) ss.addfinalizer(fin1, item) ss.addfinalizer(fin2, item) with pytest.raises(Exception) as err: - ss._callfinalizers(item) + ss.teardown_exact(item, None) assert err.value.args == ("oops2",) def test_teardown_multiple_scopes_one_fails(self, pytester: Pytester) -> None: From 2b14edb108f32c49c15b5e0c0ef4d27880b09e0f Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 1 Jan 2021 12:08:12 +0200 Subject: [PATCH 0083/2772] runner: express SetupState.teardown_all() in terms of teardown_exact() and remove it Makes it easier to understand with fewer methods. --- src/_pytest/runner.py | 13 +++++-------- testing/python/fixtures.py | 2 +- testing/test_runner.py | 13 +++++++------ 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 5d35f520ac6..525aafc76db 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -105,7 +105,7 @@ def pytest_sessionstart(session: "Session") -> None: def pytest_sessionfinish(session: "Session") -> None: - session._setupstate.teardown_all() + session._setupstate.teardown_exact(None) def pytest_runtest_protocol(item: Item, nextitem: Optional[Item]) -> bool: @@ -177,7 +177,7 @@ def pytest_runtest_call(item: Item) -> None: def pytest_runtest_teardown(item: Item, nextitem: Optional[Item]) -> None: _update_current_test_var(item, "teardown") - item.session._setupstate.teardown_exact(item, nextitem) + item.session._setupstate.teardown_exact(nextitem) _update_current_test_var(item, None) @@ -455,7 +455,7 @@ def _pop_and_teardown(self) -> None: for colitem in self._finalizers: assert colitem in self.stack - def teardown_exact(self, item: Item, nextitem: Optional[Item]) -> None: + def teardown_exact(self, nextitem: Optional[Item]) -> None: needed_collectors = nextitem and nextitem.listchain() or [] exc = None while self.stack: @@ -470,11 +470,8 @@ def teardown_exact(self, item: Item, nextitem: Optional[Item]) -> None: exc = e if exc: raise exc - - def teardown_all(self) -> None: - while self.stack: - self._pop_and_teardown() - assert not self._finalizers + if nextitem is None: + assert not self._finalizers def collect_one_node(collector: Collector) -> CollectReport: diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index 12340e690eb..d1297339645 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -856,7 +856,7 @@ def test_func(something): pass teardownlist = parent.obj.teardownlist ss = item.session._setupstate assert not teardownlist - ss.teardown_exact(item, None) + ss.teardown_exact(None) print(ss.stack) assert teardownlist == [1] diff --git a/testing/test_runner.py b/testing/test_runner.py index 20c81a62f22..aca1bd7ceb0 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -34,9 +34,10 @@ def test_setup(self, pytester: Pytester) -> None: def test_teardown_exact_stack_empty(self, pytester: Pytester) -> None: item = pytester.getitem("def test_func(): pass") ss = runner.SetupState() - ss.teardown_exact(item, None) - ss.teardown_exact(item, None) - ss.teardown_exact(item, None) + ss.prepare(item) + ss.teardown_exact(None) + ss.teardown_exact(None) + ss.teardown_exact(None) def test_setup_fails_and_failure_is_cached(self, pytester: Pytester) -> None: item = pytester.getitem( @@ -69,7 +70,7 @@ def fin3(): ss.addfinalizer(fin2, item) ss.addfinalizer(fin3, item) with pytest.raises(Exception) as err: - ss.teardown_exact(item, None) + ss.teardown_exact(None) assert err.value.args == ("oops",) assert r == ["fin3", "fin1"] @@ -88,7 +89,7 @@ def fin2(): ss.addfinalizer(fin1, item) ss.addfinalizer(fin2, item) with pytest.raises(Exception) as err: - ss.teardown_exact(item, None) + ss.teardown_exact(None) assert err.value.args == ("oops2",) def test_teardown_multiple_scopes_one_fails(self, pytester: Pytester) -> None: @@ -106,7 +107,7 @@ def fin_module(): ss.addfinalizer(fin_func, item) ss.prepare(item) with pytest.raises(Exception, match="oops1"): - ss.teardown_exact(item, None) + ss.teardown_exact(None) assert module_teardown From d1fcd425a3da18ecec35a6028093ebff830edd46 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 1 Jan 2021 12:51:33 +0200 Subject: [PATCH 0084/2772] runner: inline SetupState._pop_and_teardown() This will enable a simplification in the next commit. --- src/_pytest/runner.py | 37 +++++++++++++++++-------------------- testing/test_runner.py | 2 +- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 525aafc76db..8102a6019f2 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -436,25 +436,6 @@ def addfinalizer(self, finalizer: Callable[[], object], colitem: Node) -> None: # assert colitem in self.stack # some unit tests don't setup stack :/ self._finalizers.setdefault(colitem, []).append(finalizer) - def _pop_and_teardown(self) -> None: - colitem = self.stack.pop() - finalizers = self._finalizers.pop(colitem, None) - exc = None - while finalizers: - fin = finalizers.pop() - try: - fin() - except TEST_OUTCOME as e: - # XXX Only first exception will be seen by user, - # ideally all should be reported. - if exc is None: - exc = e - if exc: - raise exc - colitem.teardown() - for colitem in self._finalizers: - assert colitem in self.stack - def teardown_exact(self, nextitem: Optional[Item]) -> None: needed_collectors = nextitem and nextitem.listchain() or [] exc = None @@ -462,7 +443,23 @@ def teardown_exact(self, nextitem: Optional[Item]) -> None: if self.stack == needed_collectors[: len(self.stack)]: break try: - self._pop_and_teardown() + colitem = self.stack.pop() + finalizers = self._finalizers.pop(colitem, None) + inner_exc = None + while finalizers: + fin = finalizers.pop() + try: + fin() + except TEST_OUTCOME as e: + # XXX Only first exception will be seen by user, + # ideally all should be reported. + if inner_exc is None: + inner_exc = e + if inner_exc: + raise inner_exc + colitem.teardown() + for colitem in self._finalizers: + assert colitem in self.stack except TEST_OUTCOME as e: # XXX Only first exception will be seen by user, # ideally all should be reported. diff --git a/testing/test_runner.py b/testing/test_runner.py index aca1bd7ceb0..f53ad2d0820 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -28,7 +28,7 @@ def test_setup(self, pytester: Pytester) -> None: ss.prepare(item) ss.addfinalizer(values.pop, colitem=item) assert values - ss._pop_and_teardown() + ss.teardown_exact(None) assert not values def test_teardown_exact_stack_empty(self, pytester: Pytester) -> None: From 14d71b2c228c3ee92a1e7aa93a6ea64444f19697 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 1 Jan 2021 13:55:49 +0200 Subject: [PATCH 0085/2772] runner: make sure SetupState._finalizers is always set for a node in the stack This makes the stack <-> _finalizers correspondence clearer. --- src/_pytest/runner.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 8102a6019f2..b25438c2326 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -423,6 +423,7 @@ def prepare(self, colitem: Item) -> None: needed_collectors = colitem.listchain() for col in needed_collectors[len(self.stack) :]: self.stack.append(col) + self._finalizers.setdefault(col, []) try: col.setup() except TEST_OUTCOME as e: @@ -444,7 +445,7 @@ def teardown_exact(self, nextitem: Optional[Item]) -> None: break try: colitem = self.stack.pop() - finalizers = self._finalizers.pop(colitem, None) + finalizers = self._finalizers.pop(colitem) inner_exc = None while finalizers: fin = finalizers.pop() From bb3d43c9a6d16174a05058686b9460ceff911e5a Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 1 Jan 2021 13:10:43 +0200 Subject: [PATCH 0086/2772] runner: ensure item.teardown() is called even if a finalizer raised If one finalizer fails, all of the subsequent finalizers still run, so the `teardown()` method should behave the same. --- src/_pytest/runner.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index b25438c2326..c221b42a045 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -446,6 +446,7 @@ def teardown_exact(self, nextitem: Optional[Item]) -> None: try: colitem = self.stack.pop() finalizers = self._finalizers.pop(colitem) + finalizers.insert(0, colitem.teardown) inner_exc = None while finalizers: fin = finalizers.pop() @@ -456,11 +457,10 @@ def teardown_exact(self, nextitem: Optional[Item]) -> None: # ideally all should be reported. if inner_exc is None: inner_exc = e - if inner_exc: - raise inner_exc - colitem.teardown() for colitem in self._finalizers: assert colitem in self.stack + if inner_exc: + raise inner_exc except TEST_OUTCOME as e: # XXX Only first exception will be seen by user, # ideally all should be reported. From 0d4121d24bf4c1efdee67a45b831ccdbdda78f2c Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 1 Jan 2021 13:06:50 +0200 Subject: [PATCH 0087/2772] runner: collapse exception handling in SetupState.teardown_exact() This is equivalent but simpler. --- src/_pytest/runner.py | 37 ++++++++++++++----------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index c221b42a045..3aa0a6c4bf9 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -443,29 +443,20 @@ def teardown_exact(self, nextitem: Optional[Item]) -> None: while self.stack: if self.stack == needed_collectors[: len(self.stack)]: break - try: - colitem = self.stack.pop() - finalizers = self._finalizers.pop(colitem) - finalizers.insert(0, colitem.teardown) - inner_exc = None - while finalizers: - fin = finalizers.pop() - try: - fin() - except TEST_OUTCOME as e: - # XXX Only first exception will be seen by user, - # ideally all should be reported. - if inner_exc is None: - inner_exc = e - for colitem in self._finalizers: - assert colitem in self.stack - if inner_exc: - raise inner_exc - except TEST_OUTCOME as e: - # XXX Only first exception will be seen by user, - # ideally all should be reported. - if exc is None: - exc = e + colitem = self.stack.pop() + finalizers = self._finalizers.pop(colitem) + finalizers.insert(0, colitem.teardown) + while finalizers: + fin = finalizers.pop() + try: + fin() + except TEST_OUTCOME as e: + # XXX Only first exception will be seen by user, + # ideally all should be reported. + if exc is None: + exc = e + for colitem in self._finalizers: + assert colitem in self.stack if exc: raise exc if nextitem is None: From c83d030028806b119cb61824ed984524c9faad6d Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 1 Jan 2021 15:53:38 +0200 Subject: [PATCH 0088/2772] testing/test_runner: make SetupState tests use a proper SetupState Previously the tests (probably unintentionally) mixed a fresh SetupState and the generated item Session's SetupState, which led to some serious head scratching when prodding it a bit. --- testing/test_runner.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/testing/test_runner.py b/testing/test_runner.py index f53ad2d0820..f1038ce96bb 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -22,8 +22,8 @@ class TestSetupState: def test_setup(self, pytester: Pytester) -> None: - ss = runner.SetupState() item = pytester.getitem("def test_func(): pass") + ss = item.session._setupstate values = [1] ss.prepare(item) ss.addfinalizer(values.pop, colitem=item) @@ -33,7 +33,7 @@ def test_setup(self, pytester: Pytester) -> None: def test_teardown_exact_stack_empty(self, pytester: Pytester) -> None: item = pytester.getitem("def test_func(): pass") - ss = runner.SetupState() + ss = item.session._setupstate ss.prepare(item) ss.teardown_exact(None) ss.teardown_exact(None) @@ -47,9 +47,11 @@ def setup_module(mod): def test_func(): pass """ ) - ss = runner.SetupState() - pytest.raises(ValueError, lambda: ss.prepare(item)) - pytest.raises(ValueError, lambda: ss.prepare(item)) + ss = item.session._setupstate + with pytest.raises(ValueError): + ss.prepare(item) + with pytest.raises(ValueError): + ss.prepare(item) def test_teardown_multiple_one_fails(self, pytester: Pytester) -> None: r = [] @@ -64,7 +66,7 @@ def fin3(): r.append("fin3") item = pytester.getitem("def test_func(): pass") - ss = runner.SetupState() + ss = item.session._setupstate ss.prepare(item) ss.addfinalizer(fin1, item) ss.addfinalizer(fin2, item) @@ -84,7 +86,7 @@ def fin2(): raise Exception("oops2") item = pytester.getitem("def test_func(): pass") - ss = runner.SetupState() + ss = item.session._setupstate ss.prepare(item) ss.addfinalizer(fin1, item) ss.addfinalizer(fin2, item) @@ -102,7 +104,7 @@ def fin_module(): module_teardown.append("fin_module") item = pytester.getitem("def test_func(): pass") - ss = runner.SetupState() + ss = item.session._setupstate ss.addfinalizer(fin_module, item.listchain()[-2]) ss.addfinalizer(fin_func, item) ss.prepare(item) From 637300d13d69848226f0f6bbc24102dafdfd6357 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 1 Jan 2021 16:05:54 +0200 Subject: [PATCH 0089/2772] testing: fix some tests to be more realistic Perform the operations in the order and context in which they can legally occur. --- testing/python/fixtures.py | 15 +++++++++++---- testing/test_runner.py | 7 ++++--- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index d1297339645..3d78ebf5826 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -130,7 +130,8 @@ def test_funcarg_basic(self, pytester: Pytester) -> None: pytester.copy_example() item = pytester.getitem(Path("test_funcarg_basic.py")) assert isinstance(item, Function) - item._request._fillfixtures() + # Execute's item's setup, which fills fixtures. + item.session._setupstate.prepare(item) del item.funcargs["request"] assert len(get_public_names(item.funcargs)) == 2 assert item.funcargs["some"] == "test_func" @@ -809,18 +810,25 @@ def test_getfixturevalue(self, pytester: Pytester) -> None: item = pytester.getitem( """ import pytest - values = [2] + @pytest.fixture - def something(request): return 1 + def something(request): + return 1 + + values = [2] @pytest.fixture def other(request): return values.pop() + def test_func(something): pass """ ) assert isinstance(item, Function) req = item._request + # Execute item's setup. + item.session._setupstate.prepare(item) + with pytest.raises(pytest.FixtureLookupError): req.getfixturevalue("notexists") val = req.getfixturevalue("something") @@ -831,7 +839,6 @@ def test_func(something): pass assert val2 == 2 val2 = req.getfixturevalue("other") # see about caching assert val2 == 2 - item._request._fillfixtures() assert item.funcargs["something"] == 1 assert len(get_public_names(item.funcargs)) == 2 assert "request" in item.funcargs diff --git a/testing/test_runner.py b/testing/test_runner.py index f1038ce96bb..0e90ea9cca2 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -104,13 +104,14 @@ def fin_module(): module_teardown.append("fin_module") item = pytester.getitem("def test_func(): pass") + mod = item.listchain()[-2] ss = item.session._setupstate - ss.addfinalizer(fin_module, item.listchain()[-2]) - ss.addfinalizer(fin_func, item) ss.prepare(item) + ss.addfinalizer(fin_module, mod) + ss.addfinalizer(fin_func, item) with pytest.raises(Exception, match="oops1"): ss.teardown_exact(None) - assert module_teardown + assert module_teardown == ["fin_module"] class BaseFunctionalTests: From 6db082a4486923637b4f427c95ab379d81b78528 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 1 Jan 2021 17:35:22 +0200 Subject: [PATCH 0090/2772] fixtures: make sure to properly setup stack for _fill_fixtures_impl This code is weird, dead, deprecated and will be removed in pytest 7, but for now some tests execute it, so fix it up in preparation for some changes. --- src/_pytest/fixtures.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 43a40a86449..481bda8f497 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -372,6 +372,7 @@ def _fill_fixtures_impl(function: "Function") -> None: fi = fm.getfixtureinfo(function.parent, function.obj, None) function._fixtureinfo = fi request = function._request = FixtureRequest(function, _ispytest=True) + fm.session._setupstate.prepare(function) request._fillfixtures() # Prune out funcargs for jstests. newfuncargs = {} From 960ebae943927805091153bfe17677c5f3734198 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 1 Jan 2021 14:13:39 +0200 Subject: [PATCH 0091/2772] runner: enable a commented assertion in SetupState.addfinalizer The assertion ensures that when `addfinalizer(finalizer, node)` is called, the node is in the stack. This then would ensure that the finalization is actually properly executed properly during the node's teardown. Anything else indicates something is wrong. Previous commits fixed all of the tests which previously failed this, so can be reenabeld now. --- src/_pytest/runner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 3aa0a6c4bf9..9759441bd1e 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -434,8 +434,8 @@ def addfinalizer(self, finalizer: Callable[[], object], colitem: Node) -> None: """Attach a finalizer to the given colitem.""" assert colitem and not isinstance(colitem, tuple) assert callable(finalizer) - # assert colitem in self.stack # some unit tests don't setup stack :/ - self._finalizers.setdefault(colitem, []).append(finalizer) + assert colitem in self.stack, (colitem, self.stack) + self._finalizers[colitem].append(finalizer) def teardown_exact(self, nextitem: Optional[Item]) -> None: needed_collectors = nextitem and nextitem.listchain() or [] From 03c3a90c686c4cb0a146e4139125b30cba27075a Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 1 Jan 2021 21:48:03 +0200 Subject: [PATCH 0092/2772] runner: replace setdefault with an unconditional set The already-exists case is not supposed to happen. --- src/_pytest/runner.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 9759441bd1e..fe3590d448c 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -422,8 +422,10 @@ def prepare(self, colitem: Item) -> None: needed_collectors = colitem.listchain() for col in needed_collectors[len(self.stack) :]: + assert col not in self.stack + assert col not in self._finalizers self.stack.append(col) - self._finalizers.setdefault(col, []) + self._finalizers[col] = [] try: col.setup() except TEST_OUTCOME as e: From 1db78bec311b9ad161dd201a1796abf82feeb8a8 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 1 Jan 2021 21:50:38 +0200 Subject: [PATCH 0093/2772] runner: use insertion-ordered dict instead of stack, dict pair Since dicts are now ordered, we can use the finalizers dict itself as the dict, simplifying the code. --- src/_pytest/runner.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index fe3590d448c..5dbb26aef0b 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -406,8 +406,7 @@ class SetupState: """Shared state for setting up/tearing down test items or collectors.""" def __init__(self) -> None: - self.stack: List[Node] = [] - self._finalizers: Dict[Node, List[Callable[[], object]]] = {} + self.stack: Dict[Node, List[Callable[[], object]]] = {} _prepare_exc_key = StoreKey[Union[OutcomeException, Exception]]() @@ -423,9 +422,7 @@ def prepare(self, colitem: Item) -> None: needed_collectors = colitem.listchain() for col in needed_collectors[len(self.stack) :]: assert col not in self.stack - assert col not in self._finalizers - self.stack.append(col) - self._finalizers[col] = [] + self.stack[col] = [] try: col.setup() except TEST_OUTCOME as e: @@ -437,16 +434,15 @@ def addfinalizer(self, finalizer: Callable[[], object], colitem: Node) -> None: assert colitem and not isinstance(colitem, tuple) assert callable(finalizer) assert colitem in self.stack, (colitem, self.stack) - self._finalizers[colitem].append(finalizer) + self.stack[colitem].append(finalizer) def teardown_exact(self, nextitem: Optional[Item]) -> None: needed_collectors = nextitem and nextitem.listchain() or [] exc = None while self.stack: - if self.stack == needed_collectors[: len(self.stack)]: + if list(self.stack.keys()) == needed_collectors[: len(self.stack)]: break - colitem = self.stack.pop() - finalizers = self._finalizers.pop(colitem) + colitem, finalizers = self.stack.popitem() finalizers.insert(0, colitem.teardown) while finalizers: fin = finalizers.pop() @@ -457,12 +453,10 @@ def teardown_exact(self, nextitem: Optional[Item]) -> None: # ideally all should be reported. if exc is None: exc = e - for colitem in self._finalizers: - assert colitem in self.stack if exc: raise exc if nextitem is None: - assert not self._finalizers + assert not self.stack def collect_one_node(collector: Collector) -> CollectReport: From 0d19aff562680321a4dab33b1623edb424896d24 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 1 Jan 2021 22:03:52 +0200 Subject: [PATCH 0094/2772] runner: schedule node.teardown() call already at setup This is more elegant. --- src/_pytest/runner.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 5dbb26aef0b..63f9227ecda 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -422,7 +422,7 @@ def prepare(self, colitem: Item) -> None: needed_collectors = colitem.listchain() for col in needed_collectors[len(self.stack) :]: assert col not in self.stack - self.stack[col] = [] + self.stack[col] = [col.teardown] try: col.setup() except TEST_OUTCOME as e: @@ -443,7 +443,6 @@ def teardown_exact(self, nextitem: Optional[Item]) -> None: if list(self.stack.keys()) == needed_collectors[: len(self.stack)]: break colitem, finalizers = self.stack.popitem() - finalizers.insert(0, colitem.teardown) while finalizers: fin = finalizers.pop() try: From c30feeef8b12ff2a755ce0fc61a5ed1f59e83c0c Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 1 Jan 2021 23:14:04 +0200 Subject: [PATCH 0095/2772] runner: add docstring to SetupState and improve variable naming a bit --- src/_pytest/fixtures.py | 4 +- src/_pytest/runner.py | 96 +++++++++++++++++++++++++++++++++++------ testing/test_runner.py | 2 +- 3 files changed, 87 insertions(+), 15 deletions(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 481bda8f497..269369642e3 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -544,8 +544,8 @@ def addfinalizer(self, finalizer: Callable[[], object]) -> None: self._addfinalizer(finalizer, scope=self.scope) def _addfinalizer(self, finalizer: Callable[[], object], scope) -> None: - item = self._getscopeitem(scope) - item.addfinalizer(finalizer) + node = self._getscopeitem(scope) + node.addfinalizer(finalizer) def applymarker(self, marker: Union[str, MarkDecorator]) -> None: """Apply a marker to a single test function invocation. diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 63f9227ecda..7bb92cecf81 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -403,23 +403,86 @@ def pytest_make_collect_report(collector: Collector) -> CollectReport: class SetupState: - """Shared state for setting up/tearing down test items or collectors.""" + """Shared state for setting up/tearing down test items or collectors + in a session. + + Suppose we have a collection tree as follows: + + + + + + + + The SetupState maintains a stack. The stack starts out empty: + + [] + + During the setup phase of item1, prepare(item1) is called. What it does + is: + + push session to stack, run session.setup() + push mod1 to stack, run mod1.setup() + push item1 to stack, run item1.setup() + + The stack is: + + [session, mod1, item1] + + While the stack is in this shape, it is allowed to add finalizers to + each of session, mod1, item1 using addfinalizer(). + + During the teardown phase of item1, teardown_exact(item2) is called, + where item2 is the next item to item1. What it does is: + + pop item1 from stack, run its teardowns + pop mod1 from stack, run its teardowns + + mod1 was popped because it ended its purpose with item1. The stack is: + + [session] + + During the setup phase of item2, prepare(item2) is called. What it does + is: + + push mod2 to stack, run mod2.setup() + push item2 to stack, run item2.setup() + + Stack: + + [session, mod2, item2] + + During the teardown phase of item2, teardown_exact(None) is called, + because item2 is the last item. What it does is: + + pop item2 from stack, run its teardowns + pop mod2 from stack, run its teardowns + pop session from stack, run its teardowns + + Stack: + + [] + + The end! + """ def __init__(self) -> None: + # Maps node -> the node's finalizers. + # The stack is in the dict insertion order. self.stack: Dict[Node, List[Callable[[], object]]] = {} _prepare_exc_key = StoreKey[Union[OutcomeException, Exception]]() - def prepare(self, colitem: Item) -> None: - """Setup objects along the collector chain to the test-method.""" - - # Check if the last collection node has raised an error. + def prepare(self, item: Item) -> None: + """Setup objects along the collector chain to the item.""" + # If a collector fails its setup, fail its entire subtree of items. + # The setup is not retried for each item - the same exception is used. for col in self.stack: prepare_exc = col._store.get(self._prepare_exc_key, None) if prepare_exc: raise prepare_exc - needed_collectors = colitem.listchain() + needed_collectors = item.listchain() for col in needed_collectors[len(self.stack) :]: assert col not in self.stack self.stack[col] = [col.teardown] @@ -429,20 +492,29 @@ def prepare(self, colitem: Item) -> None: col._store[self._prepare_exc_key] = e raise e - def addfinalizer(self, finalizer: Callable[[], object], colitem: Node) -> None: - """Attach a finalizer to the given colitem.""" - assert colitem and not isinstance(colitem, tuple) + def addfinalizer(self, finalizer: Callable[[], object], node: Node) -> None: + """Attach a finalizer to the given node. + + The node must be currently active in the stack. + """ + assert node and not isinstance(node, tuple) assert callable(finalizer) - assert colitem in self.stack, (colitem, self.stack) - self.stack[colitem].append(finalizer) + assert node in self.stack, (node, self.stack) + self.stack[node].append(finalizer) def teardown_exact(self, nextitem: Optional[Item]) -> None: + """Teardown the current stack up until reaching nodes that nextitem + also descends from. + + When nextitem is None (meaning we're at the last item), the entire + stack is torn down. + """ needed_collectors = nextitem and nextitem.listchain() or [] exc = None while self.stack: if list(self.stack.keys()) == needed_collectors[: len(self.stack)]: break - colitem, finalizers = self.stack.popitem() + node, finalizers = self.stack.popitem() while finalizers: fin = finalizers.pop() try: diff --git a/testing/test_runner.py b/testing/test_runner.py index 0e90ea9cca2..e3f2863079f 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -26,7 +26,7 @@ def test_setup(self, pytester: Pytester) -> None: ss = item.session._setupstate values = [1] ss.prepare(item) - ss.addfinalizer(values.pop, colitem=item) + ss.addfinalizer(values.pop, item) assert values ss.teardown_exact(None) assert not values From 48fb989a71674189bec2e732fefb8b35b89d58f5 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 24 Jan 2021 14:45:49 +0200 Subject: [PATCH 0096/2772] runner: avoid using node's store in SetupState SetupState maintains its own state, so it can store the exception itself, instead of using the node's store, which is better avoided when possible. This also reduces the lifetime of the reference-cycle-inducing exception objects which is never a bad thing. --- src/_pytest/runner.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 7bb92cecf81..ae76a247271 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -36,7 +36,6 @@ from _pytest.outcomes import OutcomeException from _pytest.outcomes import Skipped from _pytest.outcomes import TEST_OUTCOME -from _pytest.store import StoreKey if TYPE_CHECKING: from typing_extensions import Literal @@ -467,29 +466,33 @@ class SetupState: """ def __init__(self) -> None: - # Maps node -> the node's finalizers. # The stack is in the dict insertion order. - self.stack: Dict[Node, List[Callable[[], object]]] = {} - - _prepare_exc_key = StoreKey[Union[OutcomeException, Exception]]() + self.stack: Dict[ + Node, + Tuple[ + # Node's finalizers. + List[Callable[[], object]], + # Node's exception, if its setup raised. + Optional[Union[OutcomeException, Exception]], + ], + ] = {} def prepare(self, item: Item) -> None: """Setup objects along the collector chain to the item.""" # If a collector fails its setup, fail its entire subtree of items. # The setup is not retried for each item - the same exception is used. - for col in self.stack: - prepare_exc = col._store.get(self._prepare_exc_key, None) + for col, (finalizers, prepare_exc) in self.stack.items(): if prepare_exc: raise prepare_exc needed_collectors = item.listchain() for col in needed_collectors[len(self.stack) :]: assert col not in self.stack - self.stack[col] = [col.teardown] + self.stack[col] = ([col.teardown], None) try: col.setup() except TEST_OUTCOME as e: - col._store[self._prepare_exc_key] = e + self.stack[col] = (self.stack[col][0], e) raise e def addfinalizer(self, finalizer: Callable[[], object], node: Node) -> None: @@ -500,7 +503,7 @@ def addfinalizer(self, finalizer: Callable[[], object], node: Node) -> None: assert node and not isinstance(node, tuple) assert callable(finalizer) assert node in self.stack, (node, self.stack) - self.stack[node].append(finalizer) + self.stack[node][0].append(finalizer) def teardown_exact(self, nextitem: Optional[Item]) -> None: """Teardown the current stack up until reaching nodes that nextitem @@ -514,7 +517,7 @@ def teardown_exact(self, nextitem: Optional[Item]) -> None: while self.stack: if list(self.stack.keys()) == needed_collectors[: len(self.stack)]: break - node, finalizers = self.stack.popitem() + node, (finalizers, prepare_exc) = self.stack.popitem() while finalizers: fin = finalizers.pop() try: From 83ee1a1f3b940b82223a162c4a695a84669c0ea3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Jan 2021 03:16:50 +0000 Subject: [PATCH 0097/2772] build(deps): bump pytest-cov in /testing/plugins_integration Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 2.10.1 to 2.11.1. - [Release notes](https://github.com/pytest-dev/pytest-cov/releases) - [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-cov/compare/v2.10.1...v2.11.1) Signed-off-by: dependabot[bot] --- testing/plugins_integration/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt index ae5c9a93fef..86c2a862c9b 100644 --- a/testing/plugins_integration/requirements.txt +++ b/testing/plugins_integration/requirements.txt @@ -2,7 +2,7 @@ anyio[curio,trio]==2.0.2 django==3.1.5 pytest-asyncio==0.14.0 pytest-bdd==4.0.2 -pytest-cov==2.10.1 +pytest-cov==2.11.1 pytest-django==4.1.0 pytest-flakes==4.0.3 pytest-html==3.1.1 From 2a890286f8487c8f3bdc5de90c2fd522da9fd6a7 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 25 Jan 2021 11:52:23 -0300 Subject: [PATCH 0098/2772] Merge pull request #8275 from pytest-dev/release-6.2.2 Prepare release 6.2.2 (cherry picked from commit 8220eca963472e7918ef7e108bdc1cd8ed155a4a) --- changelog/8152.bugfix.rst | 1 - changelog/8249.bugfix.rst | 1 - doc/en/announce/index.rst | 1 + doc/en/announce/release-6.2.2.rst | 21 +++++++++++++++++++++ doc/en/changelog.rst | 12 ++++++++++++ doc/en/example/parametrize.rst | 4 ++-- doc/en/getting-started.rst | 2 +- 7 files changed, 37 insertions(+), 5 deletions(-) delete mode 100644 changelog/8152.bugfix.rst delete mode 100644 changelog/8249.bugfix.rst create mode 100644 doc/en/announce/release-6.2.2.rst diff --git a/changelog/8152.bugfix.rst b/changelog/8152.bugfix.rst deleted file mode 100644 index d79a832de41..00000000000 --- a/changelog/8152.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed "()" being shown as a skip reason in the verbose test summary line when the reason is empty. diff --git a/changelog/8249.bugfix.rst b/changelog/8249.bugfix.rst deleted file mode 100644 index aa084c75738..00000000000 --- a/changelog/8249.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix the ``faulthandler`` plugin for occasions when running with ``twisted.logger`` and using ``pytest --capture=no``. diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index e7cac2a1c41..a7656c5ee26 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,7 @@ Release announcements :maxdepth: 2 + release-6.2.2 release-6.2.1 release-6.2.0 release-6.1.2 diff --git a/doc/en/announce/release-6.2.2.rst b/doc/en/announce/release-6.2.2.rst new file mode 100644 index 00000000000..c3999c53860 --- /dev/null +++ b/doc/en/announce/release-6.2.2.rst @@ -0,0 +1,21 @@ +pytest-6.2.2 +======================================= + +pytest 6.2.2 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. + +Thanks to all of the contributors to this release: + +* Adam Johnson +* Bruno Oliveira +* Chris NeJame +* Ran Benita + + +Happy testing, +The pytest Development Team diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index 6d66ad1d8dc..3e854f59971 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -28,6 +28,18 @@ with advance notice in the **Deprecations** section of releases. .. towncrier release notes start +pytest 6.2.2 (2021-01-25) +========================= + +Bug Fixes +--------- + +- `#8152 `_: Fixed "()" being shown as a skip reason in the verbose test summary line when the reason is empty. + + +- `#8249 `_: Fix the ``faulthandler`` plugin for occasions when running with ``twisted.logger`` and using ``pytest --capture=no``. + + pytest 6.2.1 (2020-12-15) ========================= diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index a65ee5f2fd9..771c7e16f28 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -637,13 +637,13 @@ Then run ``pytest`` with verbose mode and with only the ``basic`` marker: platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR - collecting ... collected 14 items / 11 deselected / 3 selected + collecting ... collected 24 items / 21 deselected / 3 selected test_pytest_param_example.py::test_eval[1+7-8] PASSED [ 33%] test_pytest_param_example.py::test_eval[basic_2+4] PASSED [ 66%] test_pytest_param_example.py::test_eval[basic_6*9] XFAIL [100%] - =============== 2 passed, 11 deselected, 1 xfailed in 0.12s ================ + =============== 2 passed, 21 deselected, 1 xfailed in 0.12s ================ As the result: diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index 09410585dc7..1275dff902e 100644 --- a/doc/en/getting-started.rst +++ b/doc/en/getting-started.rst @@ -28,7 +28,7 @@ Install ``pytest`` .. code-block:: bash $ pytest --version - pytest 6.2.1 + pytest 6.2.2 .. _`simpletest`: From 781b73bb523c705d65151d959736b017328e7227 Mon Sep 17 00:00:00 2001 From: Christian Steinmeyer Date: Mon, 25 Jan 2021 16:02:59 +0100 Subject: [PATCH 0099/2772] Mention that class variables are shared between tests Close #8252 --- doc/en/getting-started.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index 1275dff902e..28fd862cf3b 100644 --- a/doc/en/getting-started.rst +++ b/doc/en/getting-started.rst @@ -210,6 +210,8 @@ This is outlined below: FAILED test_class_demo.py::TestClassDemoInstance::test_two - assert 0 2 failed in 0.12s +Note that attributes added at class level are *class attributes*, so they will be shared between tests. + Request a unique temporary directory for functional tests -------------------------------------------------------------- From 33861098d9145d3441c317c9d9b2265654b53b05 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 25 Jan 2021 12:28:00 -0300 Subject: [PATCH 0100/2772] Only re-enable fauthandler during unconfigure if it was enabled before --- src/_pytest/faulthandler.py | 9 +++++---- testing/test_faulthandler.py | 37 +++++++++++++++++++++++++++--------- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/_pytest/faulthandler.py b/src/_pytest/faulthandler.py index 9592de82d6d..c8eb0310128 100644 --- a/src/_pytest/faulthandler.py +++ b/src/_pytest/faulthandler.py @@ -12,6 +12,7 @@ fault_handler_stderr_key = StoreKey[TextIO]() +fault_handler_originally_enabled_key = StoreKey[bool]() def pytest_addoption(parser: Parser) -> None: @@ -27,6 +28,7 @@ def pytest_configure(config: Config) -> None: stderr_fd_copy = os.dup(get_stderr_fileno()) config._store[fault_handler_stderr_key] = open(stderr_fd_copy, "w") + config._store[fault_handler_originally_enabled_key] = faulthandler.is_enabled() faulthandler.enable(file=config._store[fault_handler_stderr_key]) @@ -38,10 +40,9 @@ def pytest_unconfigure(config: Config) -> None: if fault_handler_stderr_key in config._store: config._store[fault_handler_stderr_key].close() del config._store[fault_handler_stderr_key] - # Re-enable the faulthandler, attaching it to the default sys.stderr - # so we can see crashes after pytest has finished, usually during - # garbage collection during interpreter shutdown. - faulthandler.enable(file=get_stderr_fileno()) + if config._store.get(fault_handler_originally_enabled_key, False): + # Re-enable the faulthandler if it was originally enabled. + faulthandler.enable(file=get_stderr_fileno()) def get_stderr_fileno() -> int: diff --git a/testing/test_faulthandler.py b/testing/test_faulthandler.py index 411e841a31a..5b7911f21f8 100644 --- a/testing/test_faulthandler.py +++ b/testing/test_faulthandler.py @@ -19,22 +19,41 @@ def test_crash(): assert result.ret != 0 -def test_crash_near_exit(pytester: Pytester) -> None: - """Test that fault handler displays crashes that happen even after - pytest is exiting (for example, when the interpreter is shutting down).""" +def setup_crashing_test(pytester: Pytester) -> None: pytester.makepyfile( """ - import faulthandler - import atexit - def test_ok(): - atexit.register(faulthandler._sigabrt) - """ + import faulthandler + import atexit + def test_ok(): + atexit.register(faulthandler._sigabrt) + """ ) - result = pytester.runpytest_subprocess() + + +def test_crash_during_shutdown_captured(pytester: Pytester) -> None: + """ + Re-enable faulthandler if pytest encountered it enabled during configure. + We should be able to then see crashes during interpreter shutdown. + """ + setup_crashing_test(pytester) + args = (sys.executable, "-Xfaulthandler", "-mpytest") + result = pytester.run(*args) result.stderr.fnmatch_lines(["*Fatal Python error*"]) assert result.ret != 0 +def test_crash_during_shutdown_not_captured(pytester: Pytester) -> None: + """ + Check that pytest leaves faulthandler disabled if it was not enabled during configure. + This prevents us from seeing crashes during interpreter shutdown (see #8260). + """ + setup_crashing_test(pytester) + args = (sys.executable, "-mpytest") + result = pytester.run(*args) + result.stderr.no_fnmatch_line("*Fatal Python error*") + assert result.ret != 0 + + def test_disabled(pytester: Pytester) -> None: """Test option to disable fault handler in the command line.""" pytester.makepyfile( From 6806091b9346e930a8029f4c07b66a987db9855a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Jan 2021 16:41:17 +0000 Subject: [PATCH 0101/2772] [pre-commit.ci] pre-commit autoupdate --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c8e19b283f8..9130a79a06b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -47,7 +47,7 @@ repos: hooks: - id: python-use-type-annotations - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.790 + rev: v0.800 hooks: - id: mypy files: ^(src/|testing/) From dfe933cdb4a1885f98c0067701c06c34aa388006 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 25 Jan 2021 15:21:08 -0300 Subject: [PATCH 0102/2772] Remove mypy workaround after 0.800 update --- src/_pytest/threadexception.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/_pytest/threadexception.py b/src/_pytest/threadexception.py index d084dc6e6a2..b250a52346f 100644 --- a/src/_pytest/threadexception.py +++ b/src/_pytest/threadexception.py @@ -34,11 +34,10 @@ class catch_threading_exception: """ def __init__(self) -> None: - # See https://github.com/python/typeshed/issues/4767 regarding the underscore. - self.args: Optional["threading._ExceptHookArgs"] = None - self._old_hook: Optional[Callable[["threading._ExceptHookArgs"], Any]] = None + self.args: Optional["threading.ExceptHookArgs"] = None + self._old_hook: Optional[Callable[["threading.ExceptHookArgs"], Any]] = None - def _hook(self, args: "threading._ExceptHookArgs") -> None: + def _hook(self, args: "threading.ExceptHookArgs") -> None: self.args = args def __enter__(self) -> "catch_threading_exception": From 8bb3977cb6f8d422dd0d469d9d3c8dbb87704d4f Mon Sep 17 00:00:00 2001 From: Hong Xu Date: Tue, 26 Jan 2021 00:48:01 -0800 Subject: [PATCH 0103/2772] Doc: Move the module declaration to index.rst When the declaration stays in reference.rst, it creates duplicated "pytest" symbols such as `pytest.pytest.mark.filterwarnings`. --- doc/en/index.rst | 1 + doc/en/reference.rst | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/en/index.rst b/doc/en/index.rst index 58f6c1d86c7..7c4d9394de9 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -11,6 +11,7 @@ pytest: helps you write better programs ======================================= +.. module:: pytest The ``pytest`` framework makes it easy to write small tests, yet scales to support complex functional testing for applications and libraries. diff --git a/doc/en/reference.rst b/doc/en/reference.rst index 51c52b33ae9..bc6c5670a5c 100644 --- a/doc/en/reference.rst +++ b/doc/en/reference.rst @@ -3,8 +3,6 @@ API Reference ============= -.. module:: pytest - This page contains the full reference to pytest's API. .. contents:: From 56cea26445095c5be7376dd9a6693d2f976b24ed Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 27 Jan 2021 10:23:18 +0100 Subject: [PATCH 0104/2772] Doc: Fix typo --- doc/en/usage.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/usage.rst b/doc/en/usage.rst index fbd3333dabc..0a26182d451 100644 --- a/doc/en/usage.rst +++ b/doc/en/usage.rst @@ -497,7 +497,7 @@ The plugins are automatically enabled for pytest runs, unless the ``-p no:threadexception`` (for thread exceptions) options are given on the command-line. -The warnings may be silenced selectivly using the :ref:`pytest.mark.filterwarnings ref` +The warnings may be silenced selectively using the :ref:`pytest.mark.filterwarnings ref` mark. The warning categories are :class:`pytest.PytestUnraisableExceptionWarning` and :class:`pytest.PytestUnhandledThreadExceptionWarning`. From 0b510bcc5173ae8f3f29750d2097c6ef07a5a142 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 29 Jan 2021 16:06:36 +0200 Subject: [PATCH 0105/2772] changelog: fix missing tick Messes with the rendering. --- changelog/8192.bugfix.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/8192.bugfix.rst b/changelog/8192.bugfix.rst index 5b26ecbe45c..8920b200a21 100644 --- a/changelog/8192.bugfix.rst +++ b/changelog/8192.bugfix.rst @@ -1,3 +1,3 @@ -``testdir.makefile` now silently accepts values which don't start with ``.`` to maintain backward compatibility with older pytest versions. +``testdir.makefile`` now silently accepts values which don't start with ``.`` to maintain backward compatibility with older pytest versions. ``pytester.makefile`` now issues a clearer error if the ``.`` is missing in the ``ext`` argument. From beda7a8a31a690a50d98e14263fcb2348ecb8bd6 Mon Sep 17 00:00:00 2001 From: Maximilian Cosmo Sitter <48606431+mcsitter@users.noreply.github.com> Date: Fri, 29 Jan 2021 15:19:54 +0100 Subject: [PATCH 0106/2772] Add plugin list --- .github/workflows/update-plugin-list.yml | 33 + README.rst | 2 +- changelog/5105.doc.rst | 1 + doc/en/_templates/globaltoc.html | 1 + doc/en/_templates/links.html | 1 - doc/en/contents.rst | 1 + doc/en/index.rst | 2 +- doc/en/plugin_list.rst | 831 +++++++++++++++++++++++ doc/en/plugins.rst | 2 +- doc/en/writing_plugins.rst | 2 +- scripts/update-plugin-list.py | 86 +++ 11 files changed, 957 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/update-plugin-list.yml create mode 100644 changelog/5105.doc.rst create mode 100644 doc/en/plugin_list.rst create mode 100644 scripts/update-plugin-list.py diff --git a/.github/workflows/update-plugin-list.yml b/.github/workflows/update-plugin-list.yml new file mode 100644 index 00000000000..10b5cb99478 --- /dev/null +++ b/.github/workflows/update-plugin-list.yml @@ -0,0 +1,33 @@ +name: Update Plugin List + +on: + schedule: + # Run daily at midnight. + - cron: '0 0 * * *' + workflow_dispatch: + +jobs: + createPullRequest: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: 3.8 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install packaging requests tabulate[widechars] + - name: Update Plugin List + run: python scripts/update-plugin-list.py + - name: Create Pull Request + uses: peter-evans/create-pull-request@2455e1596942c2902952003bbb574afbbe2ab2e6 + with: + commit-message: '[automated] Update plugin list' + branch: update-plugin-list/patch + delete-branch: true + branch-suffix: short-commit-hash + title: '[automated] Update plugin list' + body: '[automated] Update plugin list' diff --git a/README.rst b/README.rst index 778faf89e50..159bf1d4f75 100644 --- a/README.rst +++ b/README.rst @@ -93,7 +93,7 @@ Features - Python 3.6+ and PyPy3 -- Rich plugin architecture, with over 850+ `external plugins `_ and thriving community +- Rich plugin architecture, with over 850+ `external plugins `_ and thriving community Documentation diff --git a/changelog/5105.doc.rst b/changelog/5105.doc.rst new file mode 100644 index 00000000000..f0cc8bab7b4 --- /dev/null +++ b/changelog/5105.doc.rst @@ -0,0 +1 @@ +Add automatically generated :doc:`plugin_list`. The list is updated on a periodic schedule. diff --git a/doc/en/_templates/globaltoc.html b/doc/en/_templates/globaltoc.html index 4522eb2dec9..5fc1ea13e5b 100644 --- a/doc/en/_templates/globaltoc.html +++ b/doc/en/_templates/globaltoc.html @@ -7,6 +7,7 @@

{{ _('Table Of Contents') }}

  • API Reference
  • Examples
  • Customize
  • +
  • 3rd party plugins
  • Changelog
  • Contributing
  • Backwards Compatibility
  • diff --git a/doc/en/_templates/links.html b/doc/en/_templates/links.html index 6f27757a348..c253ecabfd2 100644 --- a/doc/en/_templates/links.html +++ b/doc/en/_templates/links.html @@ -2,7 +2,6 @@

    Useful Links

    diff --git a/doc/en/contents.rst b/doc/en/contents.rst index 58a08744ced..a439f1eda96 100644 --- a/doc/en/contents.rst +++ b/doc/en/contents.rst @@ -28,6 +28,7 @@ Full pytest documentation nose xunit_setup plugins + plugin_list writing_plugins logging reference diff --git a/doc/en/index.rst b/doc/en/index.rst index 7c4d9394de9..f74ef90a785 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -72,7 +72,7 @@ Features - Python 3.6+ and PyPy 3 -- Rich plugin architecture, with over 315+ `external plugins `_ and thriving community +- Rich plugin architecture, with over 315+ :doc:`external plugins ` and thriving community Documentation diff --git a/doc/en/plugin_list.rst b/doc/en/plugin_list.rst new file mode 100644 index 00000000000..927838ac942 --- /dev/null +++ b/doc/en/plugin_list.rst @@ -0,0 +1,831 @@ +Plugins List +============ + +PyPI projects that match "pytest-\*" are considered plugins and are listed +automatically. Packages classified as inactive are excluded. +This list contains 820 plugins. + +============================================================================================================== ======================================================================================================================================================================== ============== ===================== ============================================ +name summary last release status requires +============================================================================================================== ======================================================================================================================================================================== ============== ===================== ============================================ +`pytest-adaptavist `_ pytest plugin for generating test execution results within Jira Test Management (tm4j) Feb 05, 2020 N/A pytest (>=3.4.1) +`pytest-adf `_ Pytest plugin for writing Azure Data Factory integration tests Jun 03, 2020 4 - Beta pytest (>=3.5.0) +`pytest-aggreport `_ pytest plugin for pytest-repeat that generate aggregate report of the same test cases with additional statistics details. Jul 19, 2019 4 - Beta pytest (>=4.3.1) +`pytest-aiofiles `_ pytest fixtures for writing aiofiles tests with pyfakefs May 14, 2017 5 - Production/Stable N/A +`pytest-aiohttp `_ pytest plugin for aiohttp support Dec 05, 2017 N/A pytest +`pytest-aiohttp-client `_ Pytest `client` fixture for the Aiohttp Nov 01, 2020 N/A pytest (>=6) +`pytest-aioresponses `_ py.test integration for aioresponses Dec 21, 2020 4 - Beta pytest (>=3.5.0) +`pytest-aioworkers `_ A plugin to test aioworkers project with pytest Dec 04, 2019 4 - Beta pytest (>=3.5.0) +`pytest-airflow `_ pytest support for airflow. Apr 03, 2019 3 - Alpha pytest (>=4.4.0) +`pytest-alembic `_ A pytest plugin for verifying alembic migrations. Jul 13, 2020 N/A pytest (>=1.0) +`pytest-allclose `_ Pytest fixture extending Numpy's allclose function Jul 30, 2019 5 - Production/Stable pytest +`pytest-allure-adaptor `_ Plugin for py.test to generate allure xml reports Jan 10, 2018 N/A pytest (>=2.7.3) +`pytest-allure-adaptor2 `_ Plugin for py.test to generate allure xml reports Oct 14, 2020 N/A pytest (>=2.7.3) +`pytest-allure-dsl `_ pytest plugin to test case doc string dls instructions Oct 25, 2020 4 - Beta pytest +`pytest-alphamoon `_ Static code checks used at Alphamoon Nov 20, 2020 4 - Beta pytest (>=3.5.0) +`pytest-android `_ This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2. Feb 21, 2019 3 - Alpha pytest +`pytest-annotate `_ pytest-annotate: Generate PyAnnotate annotations from your pytest tests. Aug 23, 2019 3 - Alpha pytest (<6.0.0,>=3.2.0) +`pytest-ansible `_ Plugin for py.test to simplify calling ansible modules from tests or fixtures Oct 26, 2020 5 - Production/Stable pytest +`pytest-ansible-playbook `_ Pytest fixture which runs given ansible playbook file. Mar 08, 2019 4 - Beta N/A +`pytest-ansible-playbook-runner `_ Pytest fixture which runs given ansible playbook file. Dec 02, 2020 4 - Beta pytest (>=3.1.0) +`pytest-antilru `_ Bust functools.lru_cache when running pytest to avoid test pollution Apr 11, 2019 5 - Production/Stable pytest +`pytest-anything `_ Pytest fixtures to assert anything and something Apr 03, 2020 N/A N/A +`pytest-aoc `_ Downloads puzzle inputs for Advent of Code and synthesizes PyTest fixtures Dec 01, 2020 N/A pytest ; extra == 'dev' +`pytest-apistellar `_ apistellar plugin for pytest. Jun 18, 2019 N/A N/A +`pytest-appengine `_ AppEngine integration that works well with pytest-django Feb 27, 2017 N/A N/A +`pytest-appium `_ Pytest plugin for appium Dec 05, 2019 N/A N/A +`pytest-approvaltests `_ A plugin to use approvaltests with pytest Aug 02, 2019 4 - Beta N/A +`pytest-arraydiff `_ pytest plugin to help with comparing array output from tests Dec 06, 2018 4 - Beta pytest +`pytest-asgi-server `_ Convenient ASGI client/server fixtures for Pytest Dec 12, 2020 N/A pytest (>=5.4.1) +`pytest-asptest `_ test Answer Set Programming programs Apr 28, 2018 4 - Beta N/A +`pytest-assertutil `_ pytest-assertutil May 10, 2019 N/A N/A +`pytest-assert-utils `_ Useful assertion utilities for use with pytest Aug 25, 2020 3 - Alpha N/A +`pytest-assume `_ A pytest plugin that allows multiple failures per test Dec 08, 2020 N/A pytest (>=2.7) +`pytest-ast-back-to-python `_ A plugin for pytest devs to view how assertion rewriting recodes the AST Sep 29, 2019 4 - Beta N/A +`pytest-astropy `_ Meta-package containing dependencies for testing Jan 16, 2020 5 - Production/Stable pytest (>=4.6) +`pytest-astropy-header `_ pytest plugin to add diagnostic information to the header of the test output Dec 18, 2019 3 - Alpha pytest (>=2.8) +`pytest-ast-transformer `_ May 04, 2019 3 - Alpha pytest +`pytest-asyncio `_ Pytest support for asyncio. Jun 23, 2020 4 - Beta pytest (>=5.4.0) +`pytest-asyncio-cooperative `_ Run all your asynchronous tests cooperatively. Jan 03, 2021 4 - Beta N/A +`pytest-asyncio-network-simulator `_ pytest-asyncio-network-simulator: Plugin for pytest for simulator the network in tests Jul 31, 2018 3 - Alpha pytest (<3.7.0,>=3.3.2) +`pytest-async-mongodb `_ pytest plugin for async MongoDB Oct 18, 2017 5 - Production/Stable pytest (>=2.5.2) +`pytest-atomic `_ Skip rest of tests if previous test failed. Nov 24, 2018 4 - Beta N/A +`pytest-attrib `_ pytest plugin to select tests based on attributes similar to the nose-attrib plugin May 24, 2016 4 - Beta N/A +`pytest-austin `_ Austin plugin for pytest Oct 11, 2020 4 - Beta N/A +`pytest-autochecklog `_ automatically check condition and log all the checks Apr 25, 2015 4 - Beta N/A +`pytest-automock `_ Pytest plugin for automatical mocks creation Apr 22, 2020 N/A pytest ; extra == 'dev' +`pytest-auto-parametrize `_ pytest plugin: avoid repeating arguments in parametrize Oct 02, 2016 3 - Alpha N/A +`pytest-avoidance `_ Makes pytest skip tests that don not need rerunning May 23, 2019 4 - Beta pytest (>=3.5.0) +`pytest-aws `_ pytest plugin for testing AWS resource configurations Oct 04, 2017 4 - Beta N/A +`pytest-axe `_ pytest plugin for axe-selenium-python Nov 12, 2018 N/A pytest (>=3.0.0) +`pytest-azurepipelines `_ Formatting PyTest output for Azure Pipelines UI Jul 23, 2020 4 - Beta pytest (>=3.5.0) +`pytest-bandit `_ A bandit plugin for pytest Sep 25, 2019 4 - Beta pytest (>=3.5.0) +`pytest-base-url `_ pytest plugin for URL based testing Jun 19, 2020 5 - Production/Stable pytest (>=2.7.3) +`pytest-bdd `_ BDD for pytest Dec 07, 2020 6 - Mature pytest (>=4.3) +`pytest-bdd-splinter `_ Common steps for pytest bdd and splinter integration Aug 12, 2019 5 - Production/Stable pytest (>=4.0.0) +`pytest-bdd-web `_ A simple plugin to use with pytest Jan 02, 2020 4 - Beta pytest (>=3.5.0) +`pytest-bdd-wrappers `_ Feb 11, 2020 2 - Pre-Alpha N/A +`pytest-beakerlib `_ A pytest plugin that reports test results to the BeakerLib framework Mar 17, 2017 5 - Production/Stable pytest +`pytest-beds `_ Fixtures for testing Google Appengine (GAE) apps Jun 07, 2016 4 - Beta N/A +`pytest-bench `_ Benchmark utility that plugs into pytest. Jul 21, 2014 3 - Alpha N/A +`pytest-benchmark `_ A ``pytest`` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer. See calibration and FAQ. Jan 10, 2020 5 - Production/Stable pytest (>=3.8) +`pytest-bigchaindb `_ A BigchainDB plugin for pytest. Jan 10, 2020 4 - Beta N/A +`pytest-black `_ A pytest plugin to enable format checking with black Oct 05, 2020 4 - Beta N/A +`pytest-black-multipy `_ Allow '--black' on older Pythons Jan 14, 2021 5 - Production/Stable pytest (!=3.7.3,>=3.5) ; extra == 'testing' +`pytest-blame `_ A pytest plugin helps developers to debug by providing useful commits history. May 04, 2019 N/A pytest (>=4.4.0) +`pytest-blink1 `_ Pytest plugin to emit notifications via the Blink(1) RGB LED Jan 07, 2018 4 - Beta N/A +`pytest-blockage `_ Disable network requests during a test run. Feb 13, 2019 N/A pytest +`pytest-blocker `_ pytest plugin to mark a test as blocker and skip all other tests Sep 07, 2015 4 - Beta N/A +`pytest-board `_ Local continuous test runner with pytest and watchdog. Jan 20, 2019 N/A N/A +`pytest-bpdb `_ A py.test plug-in to enable drop to bpdb debugger on test failure. Jan 19, 2015 2 - Pre-Alpha N/A +`pytest-bravado `_ Pytest-bravado automatically generates from OpenAPI specification client fixtures. Jan 20, 2021 N/A N/A +`pytest-breed-adapter `_ A simple plugin to connect with breed-server Nov 07, 2018 4 - Beta pytest (>=3.5.0) +`pytest-briefcase `_ A pytest plugin for running tests on a Briefcase project. Jun 14, 2020 4 - Beta pytest (>=3.5.0) +`pytest-browser `_ A pytest plugin for console based browser test selection just after the collection phase Dec 10, 2016 3 - Alpha N/A +`pytest-browsermob-proxy `_ BrowserMob proxy plugin for py.test. Jun 11, 2013 4 - Beta N/A +`pytest-browserstack-local `_ ``py.test`` plugin to run ``BrowserStackLocal`` in background. Feb 09, 2018 N/A N/A +`pytest-bug `_ Pytest plugin for marking tests as a bug Jun 02, 2020 5 - Production/Stable pytest (>=3.6.0) +`pytest-bugzilla `_ py.test bugzilla integration plugin May 05, 2010 4 - Beta N/A +`pytest-bugzilla-notifier `_ A plugin that allows you to execute create, update, and read information from BugZilla bugs Jun 15, 2018 4 - Beta pytest (>=2.9.2) +`pytest-buildkite `_ Plugin for pytest that automatically publishes coverage and pytest report annotations to Buildkite. Jul 13, 2019 4 - Beta pytest (>=3.5.0) +`pytest-bwrap `_ Run your tests in Bubblewrap sandboxes Oct 26, 2018 3 - Alpha N/A +`pytest-cache `_ pytest plugin with mechanisms for caching across test runs Jun 04, 2013 3 - Alpha N/A +`pytest-cagoule `_ Pytest plugin to only run tests affected by changes Jan 01, 2020 3 - Alpha N/A +`pytest-camel-collect `_ Enable CamelCase-aware pytest class collection Aug 02, 2020 N/A pytest (>=2.9) +`pytest-canonical-data `_ A plugin which allows to compare results with canonical results, based on previous runs May 08, 2020 2 - Pre-Alpha pytest (>=3.5.0) +`pytest-caprng `_ A plugin that replays pRNG state on failure. May 02, 2018 4 - Beta N/A +`pytest-capture-deprecatedwarnings `_ pytest plugin to capture all deprecatedwarnings and put them in one file Apr 30, 2019 N/A N/A +`pytest-cases `_ Separate test code from test cases in pytest. Jan 25, 2021 5 - Production/Stable N/A +`pytest-cassandra `_ Cassandra CCM Test Fixtures for pytest Nov 04, 2017 1 - Planning N/A +`pytest-catchlog `_ py.test plugin to catch log messages. This is a fork of pytest-capturelog. Jan 24, 2016 4 - Beta pytest (>=2.6) +`pytest-catch-server `_ Pytest plugin with server for catching HTTP requests. Dec 12, 2019 5 - Production/Stable N/A +`pytest-celery `_ pytest-celery a shim pytest plugin to enable celery.contrib.pytest Aug 05, 2020 N/A N/A +`pytest-chalice `_ A set of py.test fixtures for AWS Chalice Jul 01, 2020 4 - Beta N/A +`pytest-change-report `_ turn . into √,turn F into x Sep 14, 2020 N/A pytest +`pytest-chdir `_ A pytest fixture for changing current working directory Jan 28, 2020 N/A pytest (>=5.0.0,<6.0.0) +`pytest-check `_ A pytest plugin that allows multiple failures per test. Dec 27, 2020 5 - Production/Stable N/A +`pytest-checkdocs `_ check the README when running tests Jan 01, 2021 5 - Production/Stable pytest (!=3.7.3,>=3.5) ; extra == 'testing' +`pytest-checkipdb `_ plugin to check if there are ipdb debugs left Jul 22, 2020 5 - Production/Stable pytest (>=2.9.2) +`pytest-check-links `_ Check links in files Jul 29, 2020 N/A N/A +`pytest-check-mk `_ pytest plugin to test Check_MK checks Nov 19, 2015 4 - Beta pytest +`pytest-circleci `_ py.test plugin for CircleCI May 03, 2019 N/A N/A +`pytest-circleci-parallelized `_ Parallelize pytest across CircleCI workers. Mar 26, 2019 N/A N/A +`pytest-ckan `_ Backport of CKAN 2.9 pytest plugin and fixtures to CAKN 2.8 Apr 28, 2020 4 - Beta pytest +`pytest-clarity `_ A plugin providing an alternative, colourful diff output for failing assertions. Jan 23, 2020 3 - Alpha N/A +`pytest-cldf `_ Easy quality control for CLDF datasets using pytest May 06, 2019 N/A N/A +`pytest-click `_ Py.test plugin for Click Aug 29, 2020 5 - Production/Stable pytest (>=5.0) +`pytest-clld `_ May 06, 2020 N/A pytest (>=3.6) +`pytest-cloud `_ Distributed tests planner plugin for pytest testing framework. Oct 05, 2020 6 - Mature N/A +`pytest-cloudflare-worker `_ pytest plugin for testing cloudflare workers Oct 19, 2020 4 - Beta pytest (>=6.0.0) +`pytest-cobra `_ PyTest plugin for testing Smart Contracts for Ethereum blockchain. Jun 29, 2019 3 - Alpha pytest (<4.0.0,>=3.7.1) +`pytest-codecheckers `_ pytest plugin to add source code sanity checks (pep8 and friends) Feb 13, 2010 N/A N/A +`pytest-codegen `_ Automatically create pytest test signatures Aug 23, 2020 2 - Pre-Alpha N/A +`pytest-codestyle `_ pytest plugin to run pycodestyle Mar 23, 2020 3 - Alpha N/A +`pytest-collect-formatter `_ Formatter for pytest collect output Nov 19, 2020 5 - Production/Stable N/A +`pytest-colordots `_ Colorizes the progress indicators Oct 06, 2017 5 - Production/Stable N/A +`pytest-commander `_ An interactive GUI test runner for PyTest Nov 21, 2020 N/A pytest (>=5.0.0) +`pytest-common-subject `_ pytest framework for testing different aspects of a common method Nov 12, 2020 N/A pytest (>=3.6,<7) +`pytest-concurrent `_ Concurrently execute test cases with multithread, multiprocess and gevent Jan 12, 2019 4 - Beta pytest (>=3.1.1) +`pytest-config `_ Base configurations and utilities for developing your Python project test suite with pytest. Nov 07, 2014 5 - Production/Stable N/A +`pytest-confluence-report `_ Package stands for pytest plugin to upload results into Confluence page. Nov 06, 2020 N/A N/A +`pytest-console-scripts `_ Pytest plugin for testing console scripts Nov 20, 2020 4 - Beta N/A +`pytest-consul `_ pytest plugin with fixtures for testing consul aware apps Nov 24, 2018 3 - Alpha pytest +`pytest-contextfixture `_ Define pytest fixtures as context managers. Mar 12, 2013 4 - Beta N/A +`pytest-contexts `_ A plugin to run tests written with the Contexts framework using pytest Jul 23, 2018 4 - Beta N/A +`pytest-cookies `_ The pytest plugin for your Cookiecutter templates. 🍪 Feb 14, 2020 5 - Production/Stable pytest (<6.0.0,>=3.3.0) +`pytest-couchdbkit `_ py.test extension for per-test couchdb databases using couchdbkit Apr 17, 2012 N/A N/A +`pytest-count `_ count erros and send email Jan 12, 2018 4 - Beta N/A +`pytest-cov `_ Pytest plugin for measuring coverage. Jan 20, 2021 5 - Production/Stable pytest (>=4.6) +`pytest-cover `_ Pytest plugin for measuring coverage. Forked from `pytest-cov`. Aug 01, 2015 5 - Production/Stable N/A +`pytest-coverage `_ Jun 17, 2015 N/A N/A +`pytest-coverage-context `_ Coverage dynamic context support for PyTest, including sub-processes Jan 04, 2021 4 - Beta pytest (>=6.1.0) +`pytest-cov-exclude `_ Pytest plugin for excluding tests based on coverage data Apr 29, 2016 4 - Beta pytest (>=2.8.0,<2.9.0); extra == 'dev' +`pytest-cpp `_ Use pytest's runner to discover and execute C++ tests Dec 10, 2020 4 - Beta pytest (!=5.4.0,!=5.4.1) +`pytest-cram `_ Run cram tests with pytest. Aug 08, 2020 N/A N/A +`pytest-crate `_ Manages CrateDB instances during your integration tests May 28, 2019 3 - Alpha pytest (>=4.0) +`pytest-cricri `_ A Cricri plugin for pytest. Jan 27, 2018 N/A pytest +`pytest-crontab `_ add crontab task in crontab Dec 09, 2019 N/A N/A +`pytest-csv `_ CSV output for pytest. Jun 24, 2019 N/A pytest (>=4.4) +`pytest-curio `_ Pytest support for curio. Oct 07, 2020 N/A N/A +`pytest-curl-report `_ pytest plugin to generate curl command line report Dec 11, 2016 4 - Beta N/A +`pytest-custom-exit-code `_ Exit pytest test session with custom exit code in different scenarios Aug 07, 2019 4 - Beta pytest (>=4.0.2) +`pytest-custom-report `_ Configure the symbols displayed for test outcomes Jan 30, 2019 N/A pytest +`pytest-cython `_ A plugin for testing Cython extension modules Jan 26, 2021 4 - Beta pytest (>=2.7.3) +`pytest-darker `_ A pytest plugin for checking of modified code using Darker Aug 16, 2020 N/A pytest (>=6.0.1) ; extra == 'test' +`pytest-dash `_ pytest fixtures to run dash applications. Mar 18, 2019 N/A N/A +`pytest-data `_ Useful functions for managing data for pytest fixtures Nov 01, 2016 5 - Production/Stable N/A +`pytest-databricks `_ Pytest plugin for remote Databricks notebooks testing Jul 29, 2020 N/A pytest +`pytest-datadir `_ pytest plugin for test data directories and files Oct 22, 2019 5 - Production/Stable pytest (>=2.7.0) +`pytest-datadir-mgr `_ Manager for test data providing downloads, caching of generated files, and a context for temp directories. Jan 08, 2021 5 - Production/Stable pytest (>=6.0.1,<7.0.0) +`pytest-datadir-ng `_ Fixtures for pytest allowing test functions/methods to easily retrieve test resources from the local filesystem. Dec 25, 2019 5 - Production/Stable pytest +`pytest-data-file `_ Fixture "data" and "case_data" for test from yaml file Dec 04, 2019 N/A N/A +`pytest-datafiles `_ py.test plugin to create a 'tmpdir' containing predefined files/directories. Oct 07, 2018 5 - Production/Stable pytest (>=3.6) +`pytest-datafixtures `_ Data fixtures for pytest made simple Dec 05, 2020 5 - Production/Stable N/A +`pytest-dataplugin `_ A pytest plugin for managing an archive of test data. Sep 16, 2017 1 - Planning N/A +`pytest-datarecorder `_ A py.test plugin recording and comparing test output. Apr 20, 2020 5 - Production/Stable pytest +`pytest-datatest `_ A pytest plugin for test driven data-wrangling (this is the development version of datatest's pytest integration). Oct 15, 2020 4 - Beta pytest (>=3.3) +`pytest-db `_ Session scope fixture "db" for mysql query or change Dec 04, 2019 N/A N/A +`pytest-dbfixtures `_ Databases fixtures plugin for py.test. Dec 07, 2016 4 - Beta N/A +`pytest-dbt-adapter `_ A pytest plugin for testing dbt adapter plugins Jan 07, 2021 N/A pytest (<7,>=6) +`pytest-dbus-notification `_ D-BUS notifications for pytest results. Mar 05, 2014 5 - Production/Stable N/A +`pytest-deadfixtures `_ A simple plugin to list unused fixtures in pytest Jul 23, 2020 5 - Production/Stable N/A +`pytest-dependency `_ Manage dependencies of tests Feb 14, 2020 4 - Beta N/A +`pytest-depends `_ Tests that depend on other tests Apr 05, 2020 5 - Production/Stable pytest (>=3) +`pytest-deprecate `_ Mark tests as testing a deprecated feature with a warning note. Jul 01, 2019 N/A N/A +`pytest-describe `_ Describe-style plugin for pytest Apr 21, 2020 3 - Alpha pytest (>=2.6.0) +`pytest-describe-it `_ plugin for rich text descriptions Jul 19, 2019 4 - Beta pytest +`pytest-devpi-server `_ DevPI server fixture for py.test May 28, 2019 5 - Production/Stable pytest +`pytest-diamond `_ pytest plugin for diamond Aug 31, 2015 4 - Beta N/A +`pytest-dicom `_ pytest plugin to provide DICOM fixtures Dec 19, 2018 3 - Alpha pytest +`pytest-dictsdiff `_ Jul 26, 2019 N/A N/A +`pytest-diff `_ A simple plugin to use with pytest Mar 30, 2019 4 - Beta pytest (>=3.5.0) +`pytest-diffeo `_ Common py.test support for Diffeo packages Apr 08, 2016 3 - Alpha N/A +`pytest-disable `_ pytest plugin to disable a test and skip it from testrun Sep 10, 2015 4 - Beta N/A +`pytest-disable-plugin `_ Disable plugins per test Feb 28, 2019 4 - Beta pytest (>=3.5.0) +`pytest-discord `_ A pytest plugin to notify test results to a Discord channel. Aug 15, 2020 3 - Alpha pytest (!=6.0.0,<7,>=3.3.2) +`pytest-django `_ A Django plugin for pytest. Oct 22, 2020 5 - Production/Stable pytest (>=5.4.0) +`pytest-django-ahead `_ A Django plugin for pytest. Oct 27, 2016 5 - Production/Stable pytest (>=2.9) +`pytest-djangoapp `_ Nice pytest plugin to help you with Django pluggable application testing. Sep 21, 2020 4 - Beta N/A +`pytest-django-cache-xdist `_ A djangocachexdist plugin for pytest May 12, 2020 4 - Beta N/A +`pytest-django-casperjs `_ Integrate CasperJS with your django tests as a pytest fixture. Mar 15, 2015 2 - Pre-Alpha N/A +`pytest-django-dotenv `_ Pytest plugin used to setup environment variables with django-dotenv Nov 26, 2019 4 - Beta pytest (>=2.6.0) +`pytest-django-factories `_ Factories for your Django models that can be used as Pytest fixtures. Nov 12, 2020 4 - Beta N/A +`pytest-django-gcir `_ A Django plugin for pytest. Mar 06, 2018 5 - Production/Stable N/A +`pytest-django-haystack `_ Cleanup your Haystack indexes between tests Sep 03, 2017 5 - Production/Stable pytest (>=2.3.4) +`pytest-django-ifactory `_ A model instance factory for pytest-django Jan 13, 2021 3 - Alpha N/A +`pytest-django-lite `_ The bare minimum to integrate py.test with Django. Jan 30, 2014 N/A N/A +`pytest-django-model `_ A Simple Way to Test your Django Models Feb 14, 2019 4 - Beta N/A +`pytest-django-ordering `_ A pytest plugin for preserving the order in which Django runs tests. Jul 25, 2019 5 - Production/Stable pytest (>=2.3.0) +`pytest-django-queries `_ Generate performance reports from your django database performance tests. Sep 03, 2020 N/A N/A +`pytest-djangorestframework `_ A djangorestframework plugin for pytest Aug 11, 2019 4 - Beta N/A +`pytest-django-rq `_ A pytest plugin to help writing unit test for django-rq Apr 13, 2020 4 - Beta N/A +`pytest-django-sqlcounts `_ py.test plugin for reporting the number of SQLs executed per django testcase. Jun 16, 2015 4 - Beta N/A +`pytest-django-testing-postgresql `_ Use a temporary PostgreSQL database with pytest-django Dec 05, 2019 3 - Alpha N/A +`pytest-doc `_ A documentation plugin for py.test. Jun 28, 2015 5 - Production/Stable N/A +`pytest-docgen `_ An RST Documentation Generator for pytest-based test suites Apr 17, 2020 N/A N/A +`pytest-docker `_ Simple pytest fixtures for Docker and docker-compose based tests Sep 22, 2020 N/A pytest (<7.0,>=4.0) +`pytest-docker-butla `_ Jun 16, 2019 3 - Alpha N/A +`pytest-dockerc `_ Run, manage and stop Docker Compose project from Docker API Oct 09, 2020 5 - Production/Stable pytest (>=3.0) +`pytest-docker-compose `_ Manages Docker containers during your integration tests Jan 26, 2021 5 - Production/Stable pytest (>=3.3) +`pytest-docker-db `_ A plugin to use docker databases for pytests Apr 19, 2020 5 - Production/Stable pytest (>=3.1.1) +`pytest-docker-fixtures `_ pytest docker fixtures Sep 30, 2020 3 - Alpha N/A +`pytest-docker-pexpect `_ pytest plugin for writing functional tests with pexpect and docker Jan 14, 2019 N/A pytest +`pytest-docker-postgresql `_ A simple plugin to use with pytest Sep 24, 2019 4 - Beta pytest (>=3.5.0) +`pytest-docker-py `_ Easy to use, simple to extend, pytest plugin that minimally leverages docker-py. Nov 27, 2018 N/A pytest (==4.0.0) +`pytest-docker-registry-fixtures `_ Pytest fixtures for testing with docker registries. Jan 25, 2021 4 - Beta pytest +`pytest-docker-tools `_ Docker integration tests for pytest Jan 15, 2021 4 - Beta pytest (>=6.0.1,<7.0.0) +`pytest-docs `_ Documentation tool for pytest Nov 11, 2018 4 - Beta pytest (>=3.5.0) +`pytest-docstyle `_ pytest plugin to run pydocstyle Mar 23, 2020 3 - Alpha N/A +`pytest-doctest-custom `_ A py.test plugin for customizing string representations of doctest results. Jul 25, 2016 4 - Beta N/A +`pytest-doctest-ellipsis-markers `_ Setup additional values for ELLIPSIS_MARKER for doctests Jan 12, 2018 4 - Beta N/A +`pytest-doctest-import `_ A simple pytest plugin to import names and add them to the doctest namespace. Nov 13, 2018 4 - Beta pytest (>=3.3.0) +`pytest-doctestplus `_ Pytest plugin with advanced doctest features. Jan 15, 2021 3 - Alpha pytest (>=4.6) +`pytest-doctest-ufunc `_ A plugin to run doctests in docstrings of Numpy ufuncs Aug 02, 2020 4 - Beta pytest (>=3.5.0) +`pytest-dolphin `_ Some extra stuff that we use ininternally Nov 30, 2016 4 - Beta pytest (==3.0.4) +`pytest-doorstop `_ A pytest plugin for adding test results into doorstop items. Jun 09, 2020 4 - Beta pytest (>=3.5.0) +`pytest-dotenv `_ A py.test plugin that parses environment files before running tests Jun 16, 2020 4 - Beta pytest (>=5.0.0) +`pytest-drf `_ A Django REST framework plugin for pytest. Nov 12, 2020 5 - Production/Stable pytest (>=3.6) +`pytest-drivings `_ Tool to allow webdriver automation to be ran locally or remotely Jan 13, 2021 N/A N/A +`pytest-drop-dup-tests `_ A Pytest plugin to drop duplicated tests during collection May 23, 2020 4 - Beta pytest (>=2.7) +`pytest-dump2json `_ A pytest plugin for dumping test results to json. Jun 29, 2015 N/A N/A +`pytest-dynamicrerun `_ A pytest plugin to rerun tests dynamically based off of test outcome and output. Aug 15, 2020 4 - Beta N/A +`pytest-dynamodb `_ DynamoDB fixtures for pytest Feb 20, 2020 5 - Production/Stable pytest (>=3.0.0) +`pytest-easy-addoption `_ pytest-easy-addoption: Easy way to work with pytest addoption Jan 22, 2020 N/A N/A +`pytest-easy-api `_ Simple API testing with pytest Mar 26, 2018 N/A N/A +`pytest-easyMPI `_ Package that supports mpi tests in pytest Oct 21, 2020 N/A N/A +`pytest-easyread `_ pytest plugin that makes terminal printouts of the reports easier to read Nov 17, 2017 N/A N/A +`pytest-ec2 `_ Pytest execution on EC2 instance Oct 22, 2019 3 - Alpha N/A +`pytest-echo `_ pytest plugin with mechanisms for echoing environment variables, package version and generic attributes Jan 08, 2020 5 - Production/Stable N/A +`pytest-elasticsearch `_ Elasticsearch process and client fixtures for py.test. Feb 19, 2020 5 - Production/Stable pytest (>=3.0.0) +`pytest-elements `_ Tool to help automate user interfaces Jan 13, 2021 N/A pytest (>=5.4,<6.0) +`pytest-elk-reporter `_ A simple plugin to use with pytest Jan 24, 2021 4 - Beta pytest (>=3.5.0) +`pytest-email `_ Send execution result email Jul 08, 2020 N/A pytest +`pytest-emoji `_ A pytest plugin that adds emojis to your test result report Feb 19, 2019 4 - Beta pytest (>=4.2.1) +`pytest-emoji-output `_ Pytest plugin to represent test output with emoji support Oct 03, 2020 4 - Beta N/A +`pytest-enabler `_ Enable installed pytest plugins Jan 19, 2021 5 - Production/Stable pytest (!=3.7.3,>=3.5) ; extra == 'testing' +`pytest-enhancements `_ Improvements for pytest (rejected upstream) Oct 30, 2019 4 - Beta N/A +`pytest-env `_ py.test plugin that allows you to add environment variables. Jun 16, 2017 4 - Beta N/A +`pytest-envfiles `_ A py.test plugin that parses environment files before running tests Oct 08, 2015 3 - Alpha N/A +`pytest-env-info `_ Push information about the running pytest into envvars Nov 25, 2017 4 - Beta pytest (>=3.1.1) +`pytest-envraw `_ py.test plugin that allows you to add environment variables. Aug 27, 2020 4 - Beta pytest (>=2.6.0) +`pytest-envvars `_ Pytest plugin to validate use of envvars on your tests Jun 13, 2020 5 - Production/Stable pytest (>=3.0.0) +`pytest-env-yaml `_ Apr 02, 2019 N/A N/A +`pytest-eradicate `_ pytest plugin to check for commented out code Sep 08, 2020 N/A pytest (>=2.4.2) +`pytest-error-for-skips `_ Pytest plugin to treat skipped tests a test failure Dec 19, 2019 4 - Beta pytest (>=4.6) +`pytest-eth `_ PyTest plugin for testing Smart Contracts for Ethereum Virtual Machine (EVM). Aug 14, 2020 1 - Planning N/A +`pytest-ethereum `_ pytest-ethereum: Pytest library for ethereum projects. Jun 24, 2019 3 - Alpha pytest (==3.3.2); extra == 'dev' +`pytest-eucalyptus `_ Pytest Plugin for BDD Aug 13, 2019 N/A pytest (>=4.2.0) +`pytest-excel `_ pytest plugin for generating excel reports Oct 06, 2020 5 - Production/Stable N/A +`pytest-exceptional `_ Better exceptions Mar 16, 2017 4 - Beta N/A +`pytest-exception-script `_ Walk your code through exception script to check it's resiliency to failures. Aug 04, 2020 3 - Alpha pytest +`pytest-executable `_ pytest plugin for testing executables Aug 10, 2020 4 - Beta pytest (<6.1,>=4.3) +`pytest-expect `_ py.test plugin to store test expectations and mark tests based on them Apr 21, 2016 4 - Beta N/A +`pytest-expecter `_ Better testing with expecter and pytest. Jul 08, 2020 5 - Production/Stable N/A +`pytest-expectr `_ This plugin is used to expect multiple assert using pytest framework. Oct 05, 2018 N/A pytest (>=2.4.2) +`pytest-exploratory `_ Interactive console for pytest. Jan 20, 2021 N/A pytest (>=5.3) +`pytest-external-blockers `_ a special outcome for tests that are blocked for external reasons Oct 04, 2016 N/A N/A +`pytest-extra-durations `_ A pytest plugin to get durations on a per-function basis and per module basis. Apr 21, 2020 4 - Beta pytest (>=3.5.0) +`pytest-fabric `_ Provides test utilities to run fabric task tests by using docker containers Sep 12, 2018 5 - Production/Stable N/A +`pytest-factory `_ Use factories for test setup with py.test Sep 06, 2020 3 - Alpha pytest (>4.3) +`pytest-factoryboy `_ Factory Boy support for pytest. Dec 30, 2020 6 - Mature pytest (>=4.6) +`pytest-factoryboy-fixtures `_ Generates pytest fixtures that allow the use of type hinting Jun 25, 2020 N/A N/A +`pytest-factoryboy-state `_ Simple factoryboy random state management Dec 11, 2020 4 - Beta pytest (>=5.0) +`pytest-failed-to-verify `_ A pytest plugin that helps better distinguishing real test failures from setup flakiness. Aug 08, 2019 5 - Production/Stable pytest (>=4.1.0) +`pytest-faker `_ Faker integration with the pytest framework. Dec 19, 2016 6 - Mature N/A +`pytest-falcon `_ Pytest helpers for Falcon. Sep 07, 2016 4 - Beta N/A +`pytest-falcon-client `_ Pytest `client` fixture for the Falcon Framework Mar 19, 2019 N/A N/A +`pytest-fantasy `_ Pytest plugin for Flask Fantasy Framework Mar 14, 2019 N/A N/A +`pytest-fastapi `_ Dec 27, 2020 N/A N/A +`pytest-fastest `_ Use SCM and coverage to run only needed tests Mar 05, 2020 N/A N/A +`pytest-faulthandler `_ py.test plugin that activates the fault handler module for tests (dummy package) Jul 04, 2019 6 - Mature pytest (>=5.0) +`pytest-fauxfactory `_ Integration of fauxfactory into pytest. Dec 06, 2017 5 - Production/Stable pytest (>=3.2) +`pytest-figleaf `_ py.test figleaf coverage plugin Jan 18, 2010 5 - Production/Stable N/A +`pytest-filedata `_ easily load data from files Jan 17, 2019 4 - Beta N/A +`pytest-filemarker `_ A pytest plugin that runs marked tests when files change. Dec 01, 2020 N/A pytest +`pytest-filter-case `_ run test cases filter by mark Nov 05, 2020 N/A N/A +`pytest-filter-subpackage `_ Pytest plugin for filtering based on sub-packages Jan 09, 2020 3 - Alpha pytest (>=3.0) +`pytest-finer-verdicts `_ A pytest plugin to treat non-assertion failures as test errors. Jun 18, 2020 N/A pytest (>=5.4.3) +`pytest-firefox `_ pytest plugin to manipulate firefox Aug 08, 2017 3 - Alpha pytest (>=3.0.2) +`pytest-fixture-config `_ Fixture configuration utils for py.test May 28, 2019 5 - Production/Stable pytest +`pytest-fixture-marker `_ A pytest plugin to add markers based on fixtures used. Oct 11, 2020 5 - Production/Stable N/A +`pytest-fixture-order `_ pytest plugin to control fixture evaluation order Aug 25, 2020 N/A pytest (>=3.0) +`pytest-fixtures `_ Common fixtures for pytest May 01, 2019 5 - Production/Stable N/A +`pytest-fixture-tools `_ Plugin for pytest which provides tools for fixtures Aug 18, 2020 6 - Mature pytest +`pytest-flake8 `_ pytest plugin to check FLAKE8 requirements Dec 16, 2020 4 - Beta pytest (>=3.5) +`pytest-flake8dir `_ A pytest fixture for testing flake8 plugins. Dec 13, 2020 5 - Production/Stable pytest +`pytest-flakefinder `_ Runs tests multiple times to expose flakiness. Jul 28, 2020 4 - Beta pytest (>=2.7.1) +`pytest-flakes `_ pytest plugin to check source code with pyflakes Nov 28, 2020 5 - Production/Stable N/A +`pytest-flaptastic `_ Flaptastic py.test plugin Mar 17, 2019 N/A N/A +`pytest-flask `_ A set of py.test fixtures to test Flask applications. Nov 09, 2020 5 - Production/Stable pytest (>=5.2) +`pytest-flask-sqlalchemy `_ A pytest plugin for preserving test isolation in Flask-SQlAlchemy using database transactions. Apr 04, 2019 4 - Beta pytest (>=3.2.1) +`pytest-flask-sqlalchemy-transactions `_ Run tests in transactions using pytest, Flask, and SQLalchemy. Aug 02, 2018 4 - Beta pytest (>=3.2.1) +`pytest-focus `_ A pytest plugin that alerts user of failed test cases with screen notifications May 04, 2019 4 - Beta pytest +`pytest-forcefail `_ py.test plugin to make the test failing regardless of pytest.mark.xfail May 15, 2018 4 - Beta N/A +`pytest-forward-compatability `_ A name to avoid typosquating pytest-foward-compatibility Sep 06, 2020 N/A N/A +`pytest-forward-compatibility `_ A pytest plugin to shim pytest commandline options for fowards compatibility Sep 29, 2020 N/A N/A +`pytest-freezegun `_ Wrap tests with fixtures in freeze_time Jul 19, 2020 4 - Beta pytest (>=3.0.0) +`pytest-freeze-reqs `_ Check if requirement files are frozen Nov 14, 2019 N/A N/A +`pytest-func-cov `_ Pytest plugin for measuring function coverage May 24, 2020 3 - Alpha pytest (>=5) +`pytest-fxa `_ pytest plugin for Firefox Accounts Aug 28, 2018 5 - Production/Stable N/A +`pytest-fxtest `_ Oct 27, 2020 N/A N/A +`pytest-gc `_ The garbage collector plugin for py.test Feb 01, 2018 N/A N/A +`pytest-gcov `_ Uses gcov to measure test coverage of a C library Feb 01, 2018 3 - Alpha N/A +`pytest-gevent `_ Ensure that gevent is properly patched when invoking pytest Feb 25, 2020 N/A pytest +`pytest-gherkin `_ A flexible framework for executing BDD gherkin tests Jul 27, 2019 3 - Alpha pytest (>=5.0.0) +`pytest-ghostinspector `_ For finding/executing Ghost Inspector tests May 17, 2016 3 - Alpha N/A +`pytest-girder `_ A set of pytest fixtures for testing Girder applications. Jan 18, 2021 N/A N/A +`pytest-git `_ Git repository fixture for py.test May 28, 2019 5 - Production/Stable pytest +`pytest-gitcov `_ Pytest plugin for reporting on coverage of the last git commit. Jan 11, 2020 2 - Pre-Alpha N/A +`pytest-git-fixtures `_ Pytest fixtures for testing with git. Jan 25, 2021 4 - Beta pytest +`pytest-github `_ Plugin for py.test that associates tests with github issues using a marker. Mar 07, 2019 5 - Production/Stable N/A +`pytest-github-actions-annotate-failures `_ pytest plugin to annotate failed tests with a workflow command for GitHub Actions Oct 13, 2020 N/A pytest (>=4.0.0) +`pytest-gitignore `_ py.test plugin to ignore the same files as git Jul 17, 2015 4 - Beta N/A +`pytest-gnupg-fixtures `_ Pytest fixtures for testing with gnupg. Jan 12, 2021 4 - Beta pytest +`pytest-golden `_ Plugin for pytest that offloads expected outputs to data files Nov 23, 2020 N/A pytest (>=6.1.2,<7.0.0) +`pytest-graphql-schema `_ Get graphql schema as fixture for pytest Oct 18, 2019 N/A N/A +`pytest-greendots `_ Green progress dots Feb 08, 2014 3 - Alpha N/A +`pytest-growl `_ Growl notifications for pytest results. Jan 13, 2014 5 - Production/Stable N/A +`pytest-grpc `_ pytest plugin for grpc May 01, 2020 N/A pytest (>=3.6.0) +`pytest-hammertime `_ Display "🔨 " instead of "." for passed pytest tests. Jul 28, 2018 N/A pytest +`pytest-harvest `_ Store data created during your pytest tests execution, and retrieve it at the end of the session, e.g. for applicative benchmarking purposes. Dec 08, 2020 5 - Production/Stable N/A +`pytest-helm-chart `_ A plugin to provide different types and configs of Kubernetes clusters that can be used for testing. Jun 15, 2020 4 - Beta pytest (>=5.4.2,<6.0.0) +`pytest-helm-charts `_ A plugin to provide different types and configs of Kubernetes clusters that can be used for testing. Dec 22, 2020 4 - Beta pytest (>=6.1.2,<7.0.0) +`pytest-helper `_ Functions to help in using the pytest testing framework May 31, 2019 5 - Production/Stable N/A +`pytest-helpers `_ pytest helpers May 17, 2020 N/A pytest +`pytest-helpers-namespace `_ PyTest Helpers Namespace Jan 07, 2019 5 - Production/Stable pytest (>=2.9.1) +`pytest-hidecaptured `_ Hide captured output May 04, 2018 4 - Beta pytest (>=2.8.5) +`pytest-historic `_ Custom report to display pytest historical execution records Apr 08, 2020 N/A pytest +`pytest-historic-hook `_ Custom listener to store execution results into MYSQL DB, which is used for pytest-historic report Apr 08, 2020 N/A pytest +`pytest-homeassistant `_ A pytest plugin for use with homeassistant custom components. Aug 12, 2020 4 - Beta N/A +`pytest-homeassistant-custom-component `_ Experimental package to automatically extract test plugins for Home Assistant custom components Jan 05, 2021 3 - Alpha pytest (==6.1.2) +`pytest-honors `_ Report on tests that honor constraints, and guard against regressions Mar 06, 2020 4 - Beta N/A +`pytest-hoverfly-wrapper `_ Integrates the Hoverfly HTTP proxy into Pytest Oct 25, 2020 4 - Beta N/A +`pytest-html `_ pytest plugin for generating HTML reports Dec 13, 2020 5 - Production/Stable pytest (!=6.0.0,>=5.0) +`pytest-html-lee `_ optimized pytest plugin for generating HTML reports Jun 30, 2020 5 - Production/Stable pytest (>=5.0) +`pytest-html-profiling `_ Pytest plugin for generating HTML reports with per-test profiling and optionally call graph visualizations. Based on pytest-html by Dave Hunt. Feb 11, 2020 5 - Production/Stable pytest (>=3.0) +`pytest-html-reporter `_ Generates a static html report based on pytest framework Sep 28, 2020 N/A N/A +`pytest-html-thread `_ pytest plugin for generating HTML reports Dec 29, 2020 5 - Production/Stable N/A +`pytest-http `_ Fixture "http" for http requests Dec 05, 2019 N/A N/A +`pytest-httpbin `_ Easily test your HTTP library against a local copy of httpbin Feb 11, 2019 5 - Production/Stable N/A +`pytest-http-mocker `_ Pytest plugin for http mocking (via https://github.com/vilus/mocker) Oct 20, 2019 N/A N/A +`pytest-httpretty `_ A thin wrapper of HTTPretty for pytest Feb 16, 2014 3 - Alpha N/A +`pytest-httpserver `_ pytest-httpserver is a httpserver for pytest Oct 18, 2020 3 - Alpha pytest ; extra == 'dev' +`pytest-httpx `_ Send responses to httpx. Nov 25, 2020 5 - Production/Stable pytest (==6.*) +`pytest-hue `_ Visualise PyTest status via your Phillips Hue lights May 09, 2019 N/A N/A +`pytest-hypo-25 `_ help hypo module for pytest Jan 12, 2020 3 - Alpha N/A +`pytest-ibutsu `_ A plugin to sent pytest results to an Ibutsu server Dec 02, 2020 4 - Beta pytest +`pytest-icdiff `_ use icdiff for better error messages in pytest assertions Apr 08, 2020 4 - Beta N/A +`pytest-idapro `_ A pytest plugin for idapython. Allows a pytest setup to run tests outside and inside IDA in an automated manner by runnig pytest inside IDA and by mocking idapython api Nov 03, 2018 N/A N/A +`pytest-ignore-flaky `_ ignore failures from flaky tests (pytest plugin) Jan 14, 2019 5 - Production/Stable pytest (>=3.7) +`pytest-image-diff `_ Sep 03, 2020 3 - Alpha pytest +`pytest-incremental `_ an incremental test runner (pytest plugin) Dec 09, 2018 4 - Beta N/A +`pytest-influxdb `_ Plugin for influxdb and pytest integration. Sep 22, 2020 N/A N/A +`pytest-info-collector `_ pytest plugin to collect information from tests May 26, 2019 3 - Alpha N/A +`pytest-informative-node `_ display more node ininformation. Apr 25, 2019 4 - Beta N/A +`pytest-infrastructure `_ pytest stack validation prior to testing executing Apr 12, 2020 4 - Beta N/A +`pytest-inmanta `_ A py.test plugin providing fixtures to simplify inmanta modules testing. Oct 12, 2020 5 - Production/Stable N/A +`pytest-inmanta-extensions `_ Inmanta tests package Nov 25, 2020 5 - Production/Stable N/A +`pytest-Inomaly `_ A simple image diff plugin for pytest Feb 13, 2018 4 - Beta N/A +`pytest-insta `_ A practical snapshot testing plugin for pytest Nov 29, 2020 N/A pytest (>=6.0.2,<7.0.0) +`pytest-instafail `_ pytest plugin to show failures instantly Jun 14, 2020 4 - Beta pytest (>=2.9) +`pytest-instrument `_ pytest plugin to instrument tests Apr 05, 2020 5 - Production/Stable pytest (>=5.1.0) +`pytest-integration `_ Organizing pytests by integration or not Apr 16, 2020 N/A N/A +`pytest-interactive `_ A pytest plugin for console based interactive test selection just after the collection phase Nov 30, 2017 3 - Alpha N/A +`pytest-invenio `_ Pytest fixtures for Invenio. Dec 17, 2020 5 - Production/Stable pytest (<7,>=6) +`pytest-involve `_ Run tests covering a specific file or changeset Feb 02, 2020 4 - Beta pytest (>=3.5.0) +`pytest-ipdb `_ A py.test plug-in to enable drop to ipdb debugger on test failure. Sep 02, 2014 2 - Pre-Alpha N/A +`pytest-ipynb `_ THIS PROJECT IS ABANDONED Jan 29, 2019 3 - Alpha N/A +`pytest-isort `_ py.test plugin to check import ordering using isort Jan 13, 2021 5 - Production/Stable N/A +`pytest-it `_ Pytest plugin to display test reports as a plaintext spec, inspired by Rspec: https://github.com/mattduck/pytest-it. Jan 22, 2020 4 - Beta N/A +`pytest-iterassert `_ Nicer list and iterable assertion messages for pytest May 11, 2020 3 - Alpha N/A +`pytest-jasmine `_ Run jasmine tests from your pytest test suite Nov 04, 2017 1 - Planning N/A +`pytest-jest `_ A custom jest-pytest oriented Pytest reporter May 22, 2018 4 - Beta pytest (>=3.3.2) +`pytest-jira `_ py.test JIRA integration plugin, using markers Nov 29, 2019 N/A N/A +`pytest-jobserver `_ Limit parallel tests with posix jobserver. May 15, 2019 5 - Production/Stable pytest +`pytest-joke `_ Test failures are better served with humor. Oct 08, 2019 4 - Beta pytest (>=4.2.1) +`pytest-json `_ Generate JSON test reports Jan 18, 2016 4 - Beta N/A +`pytest-jsonlint `_ UNKNOWN Aug 04, 2016 N/A N/A +`pytest-json-report `_ A pytest plugin to report test results as JSON files Oct 23, 2020 4 - Beta pytest (>=4.2.0) +`pytest-kafka `_ Zookeeper, Kafka server, and Kafka consumer fixtures for Pytest Nov 01, 2019 N/A pytest +`pytest-kind `_ Kubernetes test support with KIND for pytest Jan 24, 2021 5 - Production/Stable N/A +`pytest-kivy `_ Kivy GUI tests fixtures using pytest Dec 21, 2020 4 - Beta pytest (>=3.6) +`pytest-knows `_ A pytest plugin that can automaticly skip test case based on dependence info calculated by trace Aug 22, 2014 N/A N/A +`pytest-konira `_ Run Konira DSL tests with py.test Oct 09, 2011 N/A N/A +`pytest-krtech-common `_ pytest krtech common library Nov 28, 2016 4 - Beta N/A +`pytest-kwparametrize `_ Alternate syntax for @pytest.mark.parametrize with test cases as dictionaries and default value fallbacks Jan 22, 2021 N/A pytest (>=6) +`pytest-lambda `_ Define pytest fixtures with lambda functions. Dec 28, 2020 3 - Alpha pytest (>=3.6,<7) +`pytest-lamp `_ Jan 06, 2017 3 - Alpha N/A +`pytest-layab `_ Pytest fixtures for layab. Oct 05, 2020 5 - Production/Stable N/A +`pytest-lazy-fixture `_ It helps to use fixtures in pytest.mark.parametrize Feb 01, 2020 4 - Beta pytest (>=3.2.5) +`pytest-ldap `_ python-ldap fixtures for pytest Aug 18, 2020 N/A pytest +`pytest-leaks `_ A pytest plugin to trace resource leaks. Nov 27, 2019 1 - Planning N/A +`pytest-level `_ Select tests of a given level or lower Oct 21, 2019 N/A pytest +`pytest-libfaketime `_ A python-libfaketime plugin for pytest. Dec 22, 2018 4 - Beta pytest (>=3.0.0) +`pytest-libiio `_ A pytest plugin to manage interfacing with libiio contexts Jan 09, 2021 4 - Beta N/A +`pytest-libnotify `_ Pytest plugin that shows notifications about the test run Nov 12, 2018 3 - Alpha pytest +`pytest-ligo `_ Jan 16, 2020 4 - Beta N/A +`pytest-lineno `_ A pytest plugin to show the line numbers of test functions Dec 04, 2020 N/A pytest +`pytest-lisa `_ Pytest plugin for organizing tests. Jan 21, 2021 3 - Alpha pytest (>=6.1.2,<7.0.0) +`pytest-listener `_ A simple network listener May 28, 2019 5 - Production/Stable pytest +`pytest-litf `_ A pytest plugin that stream output in LITF format Jan 18, 2021 4 - Beta pytest (>=3.1.1) +`pytest-live `_ Live results for pytest Mar 08, 2020 N/A pytest +`pytest-localftpserver `_ A PyTest plugin which provides an FTP fixture for your tests Jan 27, 2021 5 - Production/Stable pytest +`pytest-localserver `_ py.test plugin to test server connections locally. Nov 14, 2018 4 - Beta N/A +`pytest-localstack `_ Pytest plugin for AWS integration tests Aug 22, 2019 4 - Beta pytest (>=3.3.0) +`pytest-lockable `_ lockable resource plugin for pytest Oct 05, 2020 3 - Alpha pytest +`pytest-locker `_ Used to lock object during testing. Essentially changing assertions from being hard coded to asserting that nothing changed Aug 11, 2020 N/A pytest (>=5.4) +`pytest-logbook `_ py.test plugin to capture logbook log messages Nov 23, 2015 5 - Production/Stable pytest (>=2.8) +`pytest-logfest `_ Pytest plugin providing three logger fixtures with basic or full writing to log files Jul 21, 2019 4 - Beta pytest (>=3.5.0) +`pytest-logger `_ Plugin configuring handlers for loggers from Python logging module. Jul 25, 2019 4 - Beta pytest (>=3.2) +`pytest-logging `_ Configures logging and allows tweaking the log level with a py.test flag Nov 04, 2015 4 - Beta N/A +`pytest-log-report `_ Package for creating a pytest test run reprot Dec 26, 2019 N/A N/A +`pytest-manual-marker `_ pytest marker for marking manual tests Nov 28, 2018 3 - Alpha pytest +`pytest-markdown `_ Test your markdown docs with pytest Jan 15, 2021 4 - Beta pytest (>=6.0.1,<7.0.0) +`pytest-marker-bugzilla `_ py.test bugzilla integration plugin, using markers Jan 09, 2020 N/A N/A +`pytest-markers-presence `_ A simple plugin to detect missed pytest tags and markers" Dec 21, 2020 4 - Beta pytest (>=6.0) +`pytest-markfiltration `_ UNKNOWN Nov 08, 2011 3 - Alpha N/A +`pytest-mark-no-py3 `_ pytest plugin and bowler codemod to help migrate tests to Python 3 May 17, 2019 N/A pytest +`pytest-marks `_ UNKNOWN Nov 23, 2012 3 - Alpha N/A +`pytest-matcher `_ Match test output against patterns stored in files Apr 23, 2020 5 - Production/Stable pytest (>=3.4) +`pytest-match-skip `_ Skip matching marks. Matches partial marks using wildcards. May 15, 2019 4 - Beta pytest (>=4.4.1) +`pytest-mat-report `_ this is report Jan 20, 2021 N/A N/A +`pytest-matrix `_ Provide tools for generating tests from combinations of fixtures. Jun 24, 2020 5 - Production/Stable pytest (>=5.4.3,<6.0.0) +`pytest-mccabe `_ pytest plugin to run the mccabe code complexity checker. Jul 22, 2020 3 - Alpha pytest (>=5.4.0) +`pytest-md `_ Plugin for generating Markdown reports for pytest results Jul 11, 2019 3 - Alpha pytest (>=4.2.1) +`pytest-md-report `_ A pytest plugin to make a test results report with Markdown table format. Aug 14, 2020 4 - Beta pytest (!=6.0.0,<7,>=3.3.2) +`pytest-memprof `_ Estimates memory consumption of test functions Mar 29, 2019 4 - Beta N/A +`pytest-menu `_ A pytest plugin for console based interactive test selection just after the collection phase Oct 04, 2017 3 - Alpha pytest (>=2.4.2) +`pytest-mercurial `_ pytest plugin to write integration tests for projects using Mercurial Python internals Nov 21, 2020 1 - Planning N/A +`pytest-messenger `_ Pytest to Slack reporting plugin Dec 16, 2020 5 - Production/Stable N/A +`pytest-metadata `_ pytest plugin for test session metadata Nov 27, 2020 5 - Production/Stable pytest (>=2.9.0) +`pytest-metrics `_ Custom metrics report for pytest Apr 04, 2020 N/A pytest +`pytest-mimesis `_ Mimesis integration with the pytest test runner Mar 21, 2020 5 - Production/Stable pytest (>=4.2) +`pytest-minecraft `_ A pytest plugin for running tests against Minecraft releases Sep 26, 2020 N/A pytest (>=6.0.1,<7.0.0) +`pytest-missing-fixtures `_ Pytest plugin that creates missing fixtures Oct 14, 2020 4 - Beta pytest (>=3.5.0) +`pytest-ml `_ Test your machine learning! May 04, 2019 4 - Beta N/A +`pytest-mocha `_ pytest plugin to display test execution output like a mochajs Apr 02, 2020 4 - Beta pytest (>=5.4.0) +`pytest-mock `_ Thin-wrapper around the mock package for easier use with pytest Jan 10, 2021 5 - Production/Stable pytest (>=5.0) +`pytest-mock-api `_ A mock API server with configurable routes and responses available as a fixture. Feb 13, 2019 1 - Planning pytest (>=4.0.0) +`pytest-mock-helper `_ Help you mock HTTP call and generate mock code Jan 24, 2018 N/A pytest +`pytest-mockito `_ Base fixtures for mockito Jul 11, 2018 4 - Beta N/A +`pytest-mockredis `_ An in-memory mock of a Redis server that runs in a separate thread. This is to be used for unit-tests that require a Redis database. Jan 02, 2018 2 - Pre-Alpha N/A +`pytest-mock-resources `_ A pytest plugin for easily instantiating reproducible mock resources. Oct 08, 2020 N/A pytest (>=1.0) +`pytest-mock-server `_ Mock server plugin for pytest Apr 06, 2020 4 - Beta N/A +`pytest-mockservers `_ A set of fixtures to test your requests to HTTP/UDP servers Mar 31, 2020 N/A pytest (>=4.3.0) +`pytest-modifyjunit `_ Utility for adding additional properties to junit xml for IDM QE Jan 10, 2019 N/A N/A +`pytest-modifyscope `_ pytest plugin to modify fixture scope Apr 12, 2020 N/A pytest +`pytest-molecule `_ PyTest Molecule Plugin :: discover and run molecule tests Jan 25, 2021 5 - Production/Stable N/A +`pytest-mongo `_ MongoDB process and client fixtures plugin for py.test. Jan 12, 2021 5 - Production/Stable pytest (>=3.0.0) +`pytest-mongodb `_ pytest plugin for MongoDB fixtures Dec 07, 2019 5 - Production/Stable pytest (>=2.5.2) +`pytest-monitor `_ Pytest plugin for analyzing resource usage. Nov 20, 2020 5 - Production/Stable pytest +`pytest-monkeyplus `_ pytest's monkeypatch subclass with extra functionalities Sep 18, 2012 5 - Production/Stable N/A +`pytest-monkeytype `_ pytest-monkeytype: Generate Monkeytype annotations from your pytest tests. Jul 29, 2020 4 - Beta N/A +`pytest-moto `_ Fixtures for integration tests of AWS services,uses moto mocking library. Aug 28, 2015 1 - Planning N/A +`pytest-mp `_ A test batcher for multiprocessed Pytest runs May 23, 2018 4 - Beta pytest +`pytest-mpi `_ pytest plugin to collect information from tests Jun 20, 2020 3 - Alpha N/A +`pytest-mpl `_ pytest plugin to help with testing figures output from Matplotlib Nov 05, 2020 4 - Beta pytest +`pytest-mproc `_ low-startup-overhead, scalable, distributed-testing pytest plugin Aug 08, 2020 4 - Beta N/A +`pytest-multihost `_ Utility for writing multi-host tests for pytest Apr 07, 2020 4 - Beta N/A +`pytest-multilog `_ Multi-process logs handling and other helpers for pytest Nov 15, 2020 N/A N/A +`pytest-mutagen `_ Add the mutation testing feature to pytest Jul 24, 2020 N/A pytest (>=5.4) +`pytest-mypy `_ Mypy static type checker plugin for Pytest Nov 14, 2020 4 - Beta pytest (>=3.5) +`pytest-mypyd `_ Mypy static type checker plugin for Pytest Aug 20, 2019 4 - Beta pytest (<4.7,>=2.8) ; python_version < "3.5" +`pytest-mypy-plugins `_ pytest plugin for writing tests for mypy plugins Oct 26, 2020 3 - Alpha pytest (>=6.0.0) +`pytest-mypy-testing `_ Pytest plugin to check mypy output. Apr 24, 2020 N/A pytest +`pytest-mysql `_ MySQL process and client fixtures for pytest Jul 21, 2020 5 - Production/Stable pytest (>=3.0.0) +`pytest-needle `_ pytest plugin for visual testing websites using selenium Dec 10, 2018 4 - Beta pytest (<5.0.0,>=3.0.0) +`pytest-neo `_ pytest-neo is a plugin for pytest that shows tests like screen of Matrix. Apr 23, 2019 3 - Alpha pytest (>=3.7.2) +`pytest-network `_ A simple plugin to disable network on socket level. May 07, 2020 N/A N/A +`pytest-nginx `_ nginx fixture for pytest Aug 12, 2017 5 - Production/Stable N/A +`pytest-nginx-iplweb `_ nginx fixture for pytest - iplweb temporary fork Mar 01, 2019 5 - Production/Stable N/A +`pytest-ngrok `_ Jan 22, 2020 3 - Alpha N/A +`pytest-ngsfixtures `_ pytest ngs fixtures Sep 06, 2019 2 - Pre-Alpha pytest (>=5.0.0) +`pytest-nice `_ A pytest plugin that alerts user of failed test cases with screen notifications May 04, 2019 4 - Beta pytest +`pytest-nocustom `_ Run all tests without custom markers May 04, 2019 5 - Production/Stable N/A +`pytest-nodev `_ Test-driven source code search for Python. Jul 21, 2016 4 - Beta pytest (>=2.8.1) +`pytest-notebook `_ A pytest plugin for testing Jupyter Notebooks Sep 16, 2020 4 - Beta pytest (>=3.5.0) +`pytest-notice `_ Send pytest execution result email Nov 05, 2020 N/A N/A +`pytest-notification `_ A pytest plugin for sending a desktop notification and playing a sound upon completion of tests Jun 19, 2020 N/A pytest (>=4) +`pytest-notifier `_ A pytest plugin to notify test result Jun 12, 2020 3 - Alpha pytest +`pytest-notimplemented `_ Pytest markers for not implemented features and tests. Aug 27, 2019 N/A pytest (>=5.1,<6.0) +`pytest-notion `_ A PyTest Reporter to send test runs to Notion.so Aug 07, 2019 N/A N/A +`pytest-nunit `_ A pytest plugin for generating NUnit3 test result XML output Aug 04, 2020 4 - Beta pytest (>=3.5.0) +`pytest-ochrus `_ pytest results data-base and HTML reporter Feb 21, 2018 4 - Beta N/A +`pytest-odoo `_ py.test plugin to run Odoo tests Aug 19, 2020 4 - Beta pytest (>=2.9) +`pytest-odoo-fixtures `_ Project description Jun 25, 2019 N/A N/A +`pytest-oerp `_ pytest plugin to test OpenERP modules Feb 28, 2012 3 - Alpha N/A +`pytest-ok `_ The ultimate pytest output plugin Apr 01, 2019 4 - Beta N/A +`pytest-only `_ Use @pytest.mark.only to run a single test Jan 19, 2020 N/A N/A +`pytest-oot `_ Run object-oriented tests in a simple format Sep 18, 2016 4 - Beta N/A +`pytest-openfiles `_ Pytest plugin for detecting inadvertent open file handles Apr 16, 2020 3 - Alpha pytest (>=4.6) +`pytest-opentmi `_ pytest plugin for publish results to opentmi Jun 10, 2020 5 - Production/Stable pytest (>=5.0) +`pytest-optional `_ include/exclude values of fixtures in pytest Oct 07, 2015 N/A N/A +`pytest-optional-tests `_ Easy declaration of optional tests (i.e., that are not run by default) Jul 09, 2019 4 - Beta pytest (>=4.5.0) +`pytest-orchestration `_ A pytest plugin for orchestrating tests Jul 18, 2019 N/A N/A +`pytest-order `_ pytest plugin to run your tests in a specific order Jan 27, 2021 4 - Beta pytest (>=3.7) +`pytest-ordering `_ pytest plugin to run your tests in a specific order Nov 14, 2018 4 - Beta pytest +`pytest-osxnotify `_ OS X notifications for py.test results. May 15, 2015 N/A N/A +`pytest-pact `_ A simple plugin to use with pytest Jan 07, 2019 4 - Beta N/A +`pytest-parallel `_ a pytest plugin for parallel and concurrent testing Apr 30, 2020 3 - Alpha pytest (>=3.0.0) +`pytest-param `_ pytest plugin to test all, first, last or random params Sep 11, 2016 4 - Beta pytest (>=2.6.0) +`pytest-paramark `_ Configure pytest fixtures using a combination of"parametrize" and markers Jan 10, 2020 4 - Beta pytest (>=4.5.0) +`pytest-parametrization `_ Simpler PyTest parametrization Jul 28, 2019 5 - Production/Stable N/A +`pytest-parametrize-cases `_ A more user-friendly way to write parametrized tests. Dec 12, 2020 N/A pytest (>=6.1.2,<7.0.0) +`pytest-parametrized `_ Pytest plugin for parametrizing tests with default iterables. Oct 19, 2020 5 - Production/Stable pytest +`pytest-parawtf `_ Finally spell paramete?ri[sz]e correctly Dec 03, 2018 4 - Beta pytest (>=3.6.0) +`pytest-pass `_ Check out https://github.com/elilutsky/pytest-pass Dec 04, 2019 N/A N/A +`pytest-paste-config `_ Allow setting the path to a paste config file Sep 18, 2013 3 - Alpha N/A +`pytest-pdb `_ pytest plugin which adds pdb helper commands related to pytest. Jul 31, 2018 N/A N/A +`pytest-peach `_ pytest plugin for fuzzing with Peach API Security Apr 12, 2019 4 - Beta pytest (>=2.8.7) +`pytest-pep257 `_ py.test plugin for pep257 Jul 09, 2016 N/A N/A +`pytest-pep8 `_ pytest plugin to check PEP8 requirements Apr 27, 2014 N/A N/A +`pytest-percent `_ Change the exit code of pytest test sessions when a required percent of tests pass. May 21, 2020 N/A pytest (>=5.2.0) +`pytest-performance `_ A simple plugin to ensure the execution of critical sections of code has not been impacted Sep 11, 2020 5 - Production/Stable pytest (>=3.7.0) +`pytest-pgsql `_ Pytest plugins and helpers for tests using a Postgres database. May 13, 2020 5 - Production/Stable pytest (>=3.0.0) +`pytest-picked `_ Run the tests related to the changed files Dec 23, 2020 N/A pytest (>=3.5.0) +`pytest-pigeonhole `_ Jun 25, 2018 5 - Production/Stable pytest (>=3.4) +`pytest-pikachu `_ Show surprise when tests are passing Sep 30, 2019 4 - Beta pytest +`pytest-pilot `_ Slice in your test base thanks to powerful markers. Oct 09, 2020 5 - Production/Stable N/A +`pytest-pings `_ 🦊 The pytest plugin for Firefox Telemetry 📊 Jun 29, 2019 3 - Alpha pytest (>=5.0.0) +`pytest-pinned `_ A simple pytest plugin for pinning tests Jan 21, 2021 4 - Beta pytest (>=3.5.0) +`pytest-pinpoint `_ A pytest plugin which runs SBFL algorithms to detect faults. Sep 25, 2020 N/A pytest (>=4.4.0) +`pytest-pipeline `_ Pytest plugin for functional testing of data analysispipelines Jan 24, 2017 3 - Alpha N/A +`pytest-platform-markers `_ Markers for pytest to skip tests on specific platforms Sep 09, 2019 4 - Beta pytest (>=3.6.0) +`pytest-play `_ pytest plugin that let you automate actions and assertions with test metrics reporting executing plain YAML files Jun 12, 2019 5 - Production/Stable N/A +`pytest-playbook `_ Pytest plugin for reading playbooks. Jan 21, 2021 3 - Alpha pytest (>=6.1.2,<7.0.0) +`pytest-playwright `_ A pytest wrapper with fixtures for Playwright to automate web browsers Jan 21, 2021 N/A pytest +`pytest-plt `_ Fixtures for quickly making Matplotlib plots in tests Aug 17, 2020 5 - Production/Stable pytest +`pytest-plugin-helpers `_ A plugin to help developing and testing other plugins Nov 23, 2019 4 - Beta pytest (>=3.5.0) +`pytest-plus `_ PyTest Plus Plugin :: extends pytest functionality Mar 19, 2020 5 - Production/Stable pytest (>=3.50) +`pytest-pmisc `_ Mar 21, 2019 5 - Production/Stable N/A +`pytest-pointers `_ Pytest plugin to define functions you test with special marks for better navigation and reports Dec 14, 2020 N/A N/A +`pytest-polarion-cfme `_ pytest plugin for collecting test cases and recording test results Nov 13, 2017 3 - Alpha N/A +`pytest-polarion-collect `_ pytest plugin for collecting polarion test cases data Jun 18, 2020 3 - Alpha pytest +`pytest-polecat `_ Provides Polecat pytest fixtures Aug 12, 2019 4 - Beta N/A +`pytest-ponyorm `_ PonyORM in Pytest Oct 31, 2018 N/A pytest (>=3.1.1) +`pytest-poo `_ Visualize your crappy tests Jul 14, 2013 5 - Production/Stable N/A +`pytest-poo-fail `_ Visualize your failed tests with poo Feb 12, 2015 5 - Production/Stable N/A +`pytest-pop `_ A pytest plugin to help with testing pop projects Aug 13, 2020 5 - Production/Stable pytest (>=5.4.0) +`pytest-postgres `_ Run PostgreSQL in Docker container in Pytest. Mar 22, 2020 N/A pytest +`pytest-postgresql `_ Postgresql fixtures and fixture factories for Pytest. Oct 29, 2020 5 - Production/Stable pytest (>=3.0.0) +`pytest-power `_ pytest plugin with powerful fixtures Dec 31, 2020 N/A pytest (>=5.4) +`pytest-pride `_ Minitest-style test colors Apr 02, 2016 3 - Alpha N/A +`pytest-print `_ pytest-print adds the printer fixture you can use to print messages to the user (directly to the pytest runner, not stdout) Oct 23, 2020 5 - Production/Stable pytest (>=3.0.0) +`pytest-profiling `_ Profiling plugin for py.test May 28, 2019 5 - Production/Stable pytest +`pytest-progress `_ pytest plugin for instant test progress status Oct 06, 2020 5 - Production/Stable N/A +`pytest-prometheus `_ Report test pass / failures to a Prometheus PushGateway Oct 03, 2017 N/A N/A +`pytest-prosper `_ Test helpers for Prosper projects Sep 24, 2018 N/A N/A +`pytest-pspec `_ A rspec format reporter for Python ptest Jun 02, 2020 4 - Beta pytest (>=3.0.0) +`pytest-pudb `_ Pytest PuDB debugger integration Oct 25, 2018 3 - Alpha pytest (>=2.0) +`pytest-purkinje `_ py.test plugin for purkinje test runner Oct 28, 2017 2 - Pre-Alpha N/A +`pytest-pycharm `_ Plugin for py.test to enter PyCharm debugger on uncaught exceptions Aug 13, 2020 5 - Production/Stable pytest (>=2.3) +`pytest-pycodestyle `_ pytest plugin to run pycodestyle Aug 10, 2020 3 - Alpha N/A +`pytest-pydev `_ py.test plugin to connect to a remote debug server with PyDev or PyCharm. Nov 15, 2017 3 - Alpha N/A +`pytest-pydocstyle `_ pytest plugin to run pydocstyle Aug 10, 2020 3 - Alpha N/A +`pytest-pylint `_ pytest plugin to check source code with pylint Nov 09, 2020 5 - Production/Stable pytest (>=5.4) +`pytest-pypi `_ Easily test your HTTP library against a local copy of pypi Mar 04, 2018 3 - Alpha N/A +`pytest-pypom-navigation `_ Core engine for cookiecutter-qa and pytest-play packages Feb 18, 2019 4 - Beta pytest (>=3.0.7) +`pytest-pyppeteer `_ A plugin to run pyppeteer in pytest. Nov 27, 2020 4 - Beta pytest (>=6.0.2) +`pytest-pyq `_ Pytest fixture "q" for pyq Mar 10, 2020 5 - Production/Stable N/A +`pytest-pyramid `_ pytest pyramid providing basic fixtures for testing pyramid applications with pytest test suite Jun 05, 2020 4 - Beta pytest +`pytest-pyramid-server `_ Pyramid server fixture for py.test May 28, 2019 5 - Production/Stable pytest +`pytest-pytestrail `_ Pytest plugin for interaction with TestRail Aug 27, 2020 4 - Beta pytest (>=3.8.0) +`pytest-pythonpath `_ pytest plugin for adding to the PYTHONPATH from command line or configs. Aug 22, 2018 5 - Production/Stable N/A +`pytest-qml `_ Run QML Tests with pytest Dec 02, 2020 4 - Beta pytest (>=6.0.0) +`pytest-qt `_ pytest support for PyQt and PySide applications Dec 07, 2019 5 - Production/Stable pytest (>=3.0.0) +`pytest-qt-app `_ QT app fixture for py.test Dec 23, 2015 5 - Production/Stable N/A +`pytest-quarantine `_ A plugin for pytest to manage expected test failures Nov 24, 2019 5 - Production/Stable pytest (>=4.6) +`pytest-quickcheck `_ pytest plugin to generate random data inspired by QuickCheck Nov 15, 2020 4 - Beta pytest (<6.0.0,>=4.0) +`pytest-rabbitmq `_ RabbitMQ process and client fixtures for pytest Jan 11, 2021 5 - Production/Stable pytest (>=3.0.0) +`pytest-race `_ Race conditions tester for pytest Nov 21, 2016 4 - Beta N/A +`pytest-rage `_ pytest plugin to implement PEP712 Oct 21, 2011 3 - Alpha N/A +`pytest-raises `_ An implementation of pytest.raises as a pytest.mark fixture Apr 23, 2020 N/A pytest (>=3.2.2) +`pytest-raisesregexp `_ Simple pytest plugin to look for regex in Exceptions Dec 18, 2015 N/A N/A +`pytest-raisin `_ Plugin enabling the use of exception instances with pytest.raises Jun 25, 2020 N/A pytest +`pytest-random `_ py.test plugin to randomize tests Apr 28, 2013 3 - Alpha N/A +`pytest-randomly `_ Pytest plugin to randomly order tests and control random.seed. Nov 16, 2020 5 - Production/Stable pytest +`pytest-randomness `_ Pytest plugin about random seed management May 30, 2019 3 - Alpha N/A +`pytest-random-num `_ Randomise the order in which pytest tests are run with some control over the randomness Oct 19, 2020 5 - Production/Stable N/A +`pytest-random-order `_ Randomise the order in which pytest tests are run with some control over the randomness Nov 30, 2018 5 - Production/Stable pytest (>=3.0.0) +`pytest-readme `_ Test your README.md file Dec 28, 2014 5 - Production/Stable N/A +`pytest-reana `_ Pytest fixtures for REANA. Nov 24, 2020 3 - Alpha N/A +`pytest-recording `_ A pytest plugin that allows you recording of network interactions via VCR.py Nov 25, 2020 4 - Beta pytest (>=3.5.0) +`pytest-recordings `_ Provides pytest plugins for reporting request/response traffic, screenshots, and more to ReportPortal Aug 13, 2020 N/A N/A +`pytest-redis `_ Redis fixtures and fixture factories for Pytest. Oct 15, 2019 5 - Production/Stable pytest (>=3.0.0) +`pytest-redmine `_ Pytest plugin for redmine Mar 19, 2018 1 - Planning N/A +`pytest-ref `_ A plugin to store reference files to ease regression testing Nov 23, 2019 4 - Beta pytest (>=3.5.0) +`pytest-reference-formatter `_ Conveniently run pytest with a dot-formatted test reference. Oct 01, 2019 4 - Beta N/A +`pytest-regressions `_ Easy to use fixtures to write regression tests. Jan 27, 2021 5 - Production/Stable pytest (>=3.5.0) +`pytest-regtest `_ pytest plugin for regression tests Sep 16, 2020 N/A N/A +`pytest-relaxed `_ Relaxed test discovery/organization for pytest Jun 14, 2019 5 - Production/Stable pytest (<5,>=3) +`pytest-remfiles `_ Pytest plugin to create a temporary directory with remote files Jul 01, 2019 5 - Production/Stable N/A +`pytest-remotedata `_ Pytest plugin for controlling remote data access. Jul 20, 2019 3 - Alpha pytest (>=3.1) +`pytest-remove-stale-bytecode `_ py.test plugin to remove stale byte code files. Mar 04, 2020 4 - Beta pytest +`pytest-reorder `_ Reorder tests depending on their paths and names. May 31, 2018 4 - Beta pytest +`pytest-repeat `_ pytest plugin for repeating tests Oct 31, 2020 5 - Production/Stable pytest (>=3.6) +`pytest-replay `_ Saves previous test runs and allow re-execute previous pytest runs to reproduce crashes or flaky tests Dec 09, 2020 4 - Beta pytest (>=3.0.0) +`pytest-repo-health `_ A pytest plugin to report on repository standards conformance Nov 03, 2020 3 - Alpha pytest +`pytest-report `_ Creates json report that is compatible with atom.io's linter message format May 11, 2016 4 - Beta N/A +`pytest-reporter `_ Generate Pytest reports with templates Nov 05, 2020 4 - Beta pytest +`pytest-reporter-html1 `_ A basic HTML report template for Pytest Nov 02, 2020 4 - Beta N/A +`pytest-reportinfra `_ Pytest plugin for reportinfra Aug 11, 2019 3 - Alpha N/A +`pytest-reporting `_ A plugin to report summarized results in a table format Oct 25, 2019 4 - Beta pytest (>=3.5.0) +`pytest-reportlog `_ Replacement for the --resultlog option, focused in simplicity and extensibility Dec 11, 2020 3 - Alpha pytest (>=5.2) +`pytest-report-me `_ A pytest plugin to generate report. Dec 31, 2020 N/A pytest +`pytest-report-parameters `_ pytest plugin for adding tests' parameters to junit report Jun 18, 2020 3 - Alpha pytest (>=2.4.2) +`pytest-reportportal `_ Agent for Reporting results of tests to the Report Portal Dec 14, 2020 N/A pytest (>=3.0.7) +`pytest-reqs `_ pytest plugin to check pinned requirements May 12, 2019 N/A pytest (>=2.4.2) +`pytest-requests `_ A simple plugin to use with pytest Jun 24, 2019 4 - Beta pytest (>=3.5.0) +`pytest-reraise `_ Make multi-threaded pytest test cases fail when they should Jun 03, 2020 5 - Production/Stable N/A +`pytest-rerun `_ Re-run only changed files in specified branch Jul 08, 2019 N/A pytest (>=3.6) +`pytest-rerunfailures `_ pytest plugin to re-run tests to eliminate flaky failures Sep 29, 2020 5 - Production/Stable pytest (>=5.0) +`pytest-resilient-circuits `_ Resilient Circuits fixtures for PyTest. Jan 21, 2021 N/A N/A +`pytest-resource `_ Load resource fixture plugin to use with pytest Nov 14, 2018 4 - Beta N/A +`pytest-resource-path `_ Provides path for uniform access to test resources in isolated directory Aug 18, 2020 5 - Production/Stable pytest (>=3.5.0) +`pytest-responsemock `_ Simplified requests calls mocking for pytest Oct 10, 2020 5 - Production/Stable N/A +`pytest-responses `_ py.test integration for responses Jan 29, 2019 N/A N/A +`pytest-restrict `_ Pytest plugin to restrict the test types allowed Dec 03, 2020 5 - Production/Stable pytest +`pytest-rethinkdb `_ A RethinkDB plugin for pytest. Jul 24, 2016 4 - Beta N/A +`pytest-reverse `_ Pytest plugin to reverse test order. Dec 27, 2020 5 - Production/Stable pytest +`pytest-ringo `_ pytest plugin to test webapplications using the Ringo webframework Sep 27, 2017 3 - Alpha N/A +`pytest-rng `_ Fixtures for seeding tests and making randomness reproducible Aug 08, 2019 5 - Production/Stable pytest +`pytest-roast `_ pytest plugin for ROAST configuration override and fixtures Jan 14, 2021 5 - Production/Stable pytest (<6) +`pytest-rotest `_ Pytest integration with rotest Sep 08, 2019 N/A pytest (>=3.5.0) +`pytest-rpc `_ Extend py.test for RPC OpenStack testing. Feb 22, 2019 4 - Beta pytest (~=3.6) +`pytest-rt `_ pytest data collector plugin for Testgr Jan 24, 2021 N/A N/A +`pytest-rts `_ Coverage-based regression test selection (RTS) plugin for pytest Dec 21, 2020 N/A pytest +`pytest-runfailed `_ implement a --failed option for pytest Mar 24, 2016 N/A N/A +`pytest-runner `_ Invoke py.test as distutils command with dependency resolution Oct 26, 2019 5 - Production/Stable pytest (!=3.7.3,>=3.5) ; extra == 'testing' +`pytest-salt `_ Pytest Salt Plugin Jan 27, 2020 4 - Beta N/A +`pytest-salt-containers `_ A Pytest plugin that builds and creates docker containers Nov 09, 2016 4 - Beta N/A +`pytest-salt-factories `_ Pytest Salt Plugin Jan 19, 2021 4 - Beta pytest (>=6.1.1) +`pytest-salt-from-filenames `_ Simple PyTest Plugin For Salt's Test Suite Specifically Jan 29, 2019 4 - Beta pytest (>=4.1) +`pytest-salt-runtests-bridge `_ Simple PyTest Plugin For Salt's Test Suite Specifically Dec 05, 2019 4 - Beta pytest (>=4.1) +`pytest-sanic `_ a pytest plugin for Sanic Sep 24, 2020 N/A pytest (>=5.2) +`pytest-sanity `_ Dec 07, 2020 N/A N/A +`pytest-sa-pg `_ May 14, 2019 N/A N/A +`pytest-sbase `_ A complete web automation framework for end-to-end testing. Jan 27, 2021 5 - Production/Stable N/A +`pytest-scenario `_ pytest plugin for test scenarios Feb 06, 2017 3 - Alpha N/A +`pytest-schema `_ 👍 Validate return values against a schema-like object in testing Aug 31, 2020 5 - Production/Stable pytest (>=3.5.0) +`pytest-securestore `_ An encrypted password store for use within pytest cases Jun 19, 2019 4 - Beta N/A +`pytest-select `_ A pytest plugin which allows to (de-)select tests from a file. Jan 18, 2019 3 - Alpha pytest (>=3.0) +`pytest-selenium `_ pytest plugin for Selenium Sep 19, 2020 5 - Production/Stable pytest (>=5.0.0) +`pytest-seleniumbase `_ A complete web automation framework for end-to-end testing. Jan 27, 2021 5 - Production/Stable N/A +`pytest-selenium-enhancer `_ pytest plugin for Selenium Nov 26, 2020 5 - Production/Stable N/A +`pytest-selenium-pdiff `_ A pytest package implementing perceptualdiff for Selenium tests. Apr 06, 2017 2 - Pre-Alpha N/A +`pytest-send-email `_ Send pytest execution result email Dec 04, 2019 N/A N/A +`pytest-sentry `_ A pytest plugin to send testrun information to Sentry.io Dec 16, 2020 N/A N/A +`pytest-server-fixtures `_ Extensible server fixures for py.test May 28, 2019 5 - Production/Stable pytest +`pytest-serverless `_ Automatically mocks resources from serverless.yml in pytest using moto. Dec 26, 2020 4 - Beta N/A +`pytest-services `_ Services plugin for pytest testing framework Oct 30, 2020 6 - Mature N/A +`pytest-session2file `_ pytest-session2file (aka: pytest-session_to_file for v0.1.0 - v0.1.2) is a py.test plugin for capturing and saving to file the stdout of py.test. Jan 26, 2021 3 - Alpha pytest +`pytest-session-fixture-globalize `_ py.test plugin to make session fixtures behave as if written in conftest, even if it is written in some modules May 15, 2018 4 - Beta N/A +`pytest-session_to_file `_ pytest-session_to_file is a py.test plugin for capturing and saving to file the stdout of py.test. Oct 01, 2015 3 - Alpha N/A +`pytest-sftpserver `_ py.test plugin to locally test sftp server connections. Sep 16, 2019 4 - Beta N/A +`pytest-shard `_ Dec 11, 2020 4 - Beta pytest +`pytest-shell `_ A pytest plugin for testing shell scripts and line-based processes Jan 18, 2020 N/A N/A +`pytest-sheraf `_ Versatile ZODB abstraction layer - pytest fixtures Feb 11, 2020 N/A pytest +`pytest-sherlock `_ pytest plugin help to find coupled tests Jul 13, 2020 5 - Production/Stable pytest (>=3.5.1) +`pytest-shortcuts `_ Expand command-line shortcuts listed in pytest configuration Oct 29, 2020 4 - Beta pytest (>=3.5.0) +`pytest-shutil `_ A goodie-bag of unix shell and environment tools for py.test May 28, 2019 5 - Production/Stable pytest +`pytest-simple-plugin `_ Simple pytest plugin Nov 27, 2019 N/A N/A +`pytest-simple-settings `_ simple-settings plugin for pytest Nov 17, 2020 4 - Beta pytest +`pytest-single-file-logging `_ Allow for multiple processes to log to a single file May 05, 2016 4 - Beta pytest (>=2.8.1) +`pytest-skipper `_ A plugin that selects only tests with changes in execution path Mar 26, 2017 3 - Alpha pytest (>=3.0.6) +`pytest-skippy `_ Automatically skip tests that don't need to run! Jan 27, 2018 3 - Alpha pytest (>=2.3.4) +`pytest-slack `_ Pytest to Slack reporting plugin Dec 15, 2020 5 - Production/Stable N/A +`pytest-smartcollect `_ A plugin for collecting tests that touch changed code Oct 04, 2018 N/A pytest (>=3.5.0) +`pytest-smartcov `_ Smart coverage plugin for pytest. Sep 30, 2017 3 - Alpha N/A +`pytest-snail `_ Plugin for adding a marker to slow running tests. 🐌 Nov 04, 2019 3 - Alpha pytest (>=5.0.1) +`pytest-snapci `_ py.test plugin for Snap-CI Nov 12, 2015 N/A N/A +`pytest-snapshot `_ A plugin to enable snapshot testing with pytest. Jan 22, 2021 4 - Beta pytest (>=3.0.0) +`pytest-snmpserver `_ Sep 14, 2020 N/A N/A +`pytest-socket `_ Pytest Plugin to disable socket calls during tests May 31, 2020 4 - Beta pytest (>=3.6.3) +`pytest-soft-assertions `_ May 05, 2020 3 - Alpha pytest +`pytest-solr `_ Solr process and client fixtures for py.test. May 11, 2020 3 - Alpha pytest (>=3.0.0) +`pytest-sorter `_ A simple plugin to first execute tests that historically failed more Jul 23, 2020 4 - Beta pytest (>=3.1.1) +`pytest-sourceorder `_ Test-ordering plugin for pytest Apr 11, 2017 4 - Beta pytest +`pytest-spark `_ pytest plugin to run the tests with support of pyspark. Feb 23, 2020 4 - Beta pytest +`pytest-spawner `_ py.test plugin to spawn process and communicate with them. Jul 31, 2015 4 - Beta N/A +`pytest-spec `_ Library pytest-spec is a pytest plugin to display test execution output like a SPECIFICATION. Jan 14, 2021 N/A N/A +`pytest-sphinx `_ Doctest plugin for pytest with support for Sphinx-specific doctest-directives Aug 05, 2020 4 - Beta N/A +`pytest-spiratest `_ Exports unit tests as test runs in SpiraTest/Team/Plan Oct 30, 2020 N/A N/A +`pytest-splinter `_ Splinter plugin for pytest testing framework Dec 25, 2020 6 - Mature N/A +`pytest-split `_ Pytest plugin for splitting test suite based on test execution time Apr 07, 2020 1 - Planning N/A +`pytest-splitio `_ Split.io SDK integration for e2e tests Sep 22, 2020 N/A pytest (<7,>=5.0) +`pytest-split-tests `_ A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. Forked from Mark Adams' original project pytest-test-groups. May 28, 2019 N/A pytest (>=2.5) +`pytest-splunk-addon `_ A Dynamic test tool for Splunk Apps and Add-ons Jan 05, 2021 N/A pytest (>5.4.0,<6.1) +`pytest-splunk-addon-ui-smartx `_ Library to support testing Splunk Add-on UX Jan 18, 2021 N/A N/A +`pytest-splunk-env `_ pytest fixtures for interaction with Splunk Enterprise and Splunk Cloud Oct 22, 2020 N/A pytest (>=6.1.1,<7.0.0) +`pytest-sqitch `_ sqitch for pytest Apr 06, 2020 4 - Beta N/A +`pytest-sqlalchemy `_ pytest plugin with sqlalchemy related fixtures Mar 13, 2018 3 - Alpha N/A +`pytest-sql-bigquery `_ Yet another SQL-testing framework for BigQuery provided by pytest plugin Dec 19, 2019 N/A pytest +`pytest-ssh `_ pytest plugin for ssh command run May 27, 2019 N/A pytest +`pytest-start-from `_ Start pytest run from a given point Apr 11, 2016 N/A N/A +`pytest-statsd `_ pytest plugin for reporting to graphite Nov 30, 2018 5 - Production/Stable pytest (>=3.0.0) +`pytest-stepfunctions `_ A small description Jul 07, 2020 4 - Beta pytest +`pytest-steps `_ Create step-wise / incremental tests in pytest. Apr 25, 2020 5 - Production/Stable N/A +`pytest-stepwise `_ Run a test suite one failing test at a time. Dec 01, 2015 4 - Beta N/A +`pytest-stoq `_ A plugin to pytest stoq Nov 04, 2020 4 - Beta N/A +`pytest-stress `_ A Pytest plugin that allows you to loop tests for a user defined amount of time. Dec 07, 2019 4 - Beta pytest (>=3.6.0) +`pytest-structlog `_ Structured logging assertions Jul 16, 2020 N/A pytest +`pytest-structmpd `_ provide structured temporary directory Oct 17, 2018 N/A N/A +`pytest-stub `_ Stub packages, modules and attributes. Apr 28, 2020 5 - Production/Stable N/A +`pytest-stubprocess `_ Provide stub implementations for subprocesses in Python tests Sep 17, 2018 3 - Alpha pytest (>=3.5.0) +`pytest-study `_ A pytest plugin to organize long run tests (named studies) without interfering the regular tests Sep 26, 2017 3 - Alpha pytest (>=2.0) +`pytest-subprocess `_ A plugin to fake subprocess for pytest Aug 22, 2020 5 - Production/Stable pytest (>=4.0.0) +`pytest-subtesthack `_ A hack to explicitly set up and tear down fixtures. Jan 31, 2016 N/A N/A +`pytest-subtests `_ unittest subTest() support and subtests fixture Dec 13, 2020 4 - Beta pytest (>=5.3.0) +`pytest-subunit `_ pytest-subunit is a plugin for py.test which outputs testsresult in subunit format. Aug 29, 2017 N/A N/A +`pytest-sugar `_ pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly). Jul 06, 2020 3 - Alpha N/A +`pytest-sugar-bugfix159 `_ Workaround for https://github.com/Frozenball/pytest-sugar/issues/159 Nov 07, 2018 5 - Production/Stable pytest (!=3.7.3,>=3.5); extra == 'testing' +`pytest-super-check `_ Pytest plugin to check your TestCase classes call super in setUp, tearDown, etc. Dec 13, 2020 5 - Production/Stable pytest +`pytest-svn `_ SVN repository fixture for py.test May 28, 2019 5 - Production/Stable pytest +`pytest-symbols `_ pytest-symbols is a pytest plugin that adds support for passing test environment symbols into pytest tests. Nov 20, 2017 3 - Alpha N/A +`pytest-tap `_ Test Anything Protocol (TAP) reporting plugin for pytest Nov 07, 2020 5 - Production/Stable pytest (>=3.0) +`pytest-target `_ Pytest plugin for remote target orchestration. Jan 21, 2021 3 - Alpha pytest (>=6.1.2,<7.0.0) +`pytest-tblineinfo `_ tblineinfo is a py.test plugin that insert the node id in the final py.test report when --tb=line option is used Dec 01, 2015 3 - Alpha pytest (>=2.0) +`pytest-teamcity-logblock `_ py.test plugin to introduce block structure in teamcity build log, if output is not captured May 15, 2018 4 - Beta N/A +`pytest-telegram `_ Pytest to Telegram reporting plugin Dec 10, 2020 5 - Production/Stable N/A +`pytest-tempdir `_ Predictable and repeatable tempdir support. Oct 11, 2019 4 - Beta pytest (>=2.8.1) +`pytest-terraform `_ A pytest plugin for using terraform fixtures Oct 20, 2020 N/A pytest (>=6.0.0,<6.1.0) +`pytest-terraform-fixture `_ generate terraform resources to use with pytest Nov 14, 2018 4 - Beta N/A +`pytest-testbook `_ A plugin to run tests written in Jupyter notebook Dec 11, 2016 3 - Alpha N/A +`pytest-testconfig `_ Test configuration plugin for pytest. Jan 11, 2020 4 - Beta pytest (>=3.5.0) +`pytest-testdirectory `_ A py.test plugin providing temporary directories in unit tests. Nov 06, 2018 5 - Production/Stable pytest +`pytest-testdox `_ A testdox format reporter for pytest Oct 13, 2020 5 - Production/Stable pytest (>=3.7.0) +`pytest-test-groups `_ A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. Oct 25, 2016 5 - Production/Stable N/A +`pytest-testinfra `_ Test infrastructures Nov 12, 2020 5 - Production/Stable pytest (!=3.0.2) +`pytest-testlink-adaptor `_ pytest reporting plugin for testlink Dec 20, 2018 4 - Beta pytest (>=2.6) +`pytest-testmon `_ selects tests affected by changed files and methods Aug 05, 2020 4 - Beta N/A +`pytest-testobject `_ Plugin to use TestObject Suites with Pytest Sep 24, 2019 4 - Beta pytest (>=3.1.1) +`pytest-testrail `_ pytest plugin for creating TestRail runs and adding results Aug 27, 2020 N/A pytest (>=3.6) +`pytest-testrail2 `_ A small example package Nov 17, 2020 N/A pytest (>=5) +`pytest-testrail-api `_ Плагин Pytest, для интеграции с TestRail Dec 09, 2020 N/A pytest (>=5.5) +`pytest-testrail-client `_ pytest plugin for Testrail Sep 29, 2020 5 - Production/Stable N/A +`pytest-testrail-e2e `_ pytest plugin for creating TestRail runs and adding results Jun 11, 2020 N/A pytest (>=3.6) +`pytest-testrail-plugin `_ PyTest plugin for TestRail Apr 21, 2020 3 - Alpha pytest +`pytest-testrail-reporter `_ Sep 10, 2018 N/A N/A +`pytest-testslide `_ TestSlide fixture for pytest Jan 07, 2021 5 - Production/Stable pytest (~=6.2) +`pytest-test-this `_ Plugin for py.test to run relevant tests, based on naively checking if a test contains a reference to the symbol you supply Sep 15, 2019 2 - Pre-Alpha pytest (>=2.3) +`pytest-tesults `_ Tesults plugin for pytest May 18, 2020 5 - Production/Stable pytest (>=3.5.0) +`pytest-tezos `_ pytest-ligo Jan 16, 2020 4 - Beta N/A +`pytest-thawgun `_ Pytest plugin for time travel May 26, 2020 3 - Alpha N/A +`pytest-threadleak `_ Detects thread leaks Sep 08, 2017 4 - Beta N/A +`pytest-timeit `_ A pytest plugin to time test function runs Oct 13, 2016 4 - Beta N/A +`pytest-timeout `_ py.test plugin to abort hanging tests Jul 15, 2020 5 - Production/Stable pytest (>=3.6.0) +`pytest-timeouts `_ Linux-only Pytest plugin to control durations of various test case execution phases Sep 21, 2019 5 - Production/Stable N/A +`pytest-timer `_ A timer plugin for pytest Dec 13, 2020 N/A N/A +`pytest-tipsi-django `_ Oct 14, 2020 4 - Beta pytest (>=6.0.0) +`pytest-tipsi-testing `_ Better fixtures management. Various helpers Nov 04, 2020 4 - Beta pytest (>=3.3.0) +`pytest-tldr `_ A pytest plugin that limits the output to just the things you need. Aug 08, 2020 4 - Beta pytest (>=3.5.0) +`pytest-tm4j-reporter `_ Cloud Jira Test Management (TM4J) PyTest reporter plugin Sep 01, 2020 N/A pytest +`pytest-todo `_ A small plugin for the pytest testing framework, marking TODO comments as failure May 23, 2019 4 - Beta pytest +`pytest-tomato `_ Mar 01, 2019 5 - Production/Stable N/A +`pytest-toolbelt `_ This is just a collection of utilities for pytest, but don't really belong in pytest proper. Aug 12, 2019 3 - Alpha N/A +`pytest-toolbox `_ Numerous useful plugins for pytest. Apr 07, 2018 N/A pytest (>=3.5.0) +`pytest-tornado `_ A py.test plugin providing fixtures and markers to simplify testing of asynchronous tornado applications. Jun 17, 2020 5 - Production/Stable pytest (>=3.6) +`pytest-tornado5 `_ A py.test plugin providing fixtures and markers to simplify testing of asynchronous tornado applications. Nov 16, 2018 5 - Production/Stable pytest (>=3.6) +`pytest-tornado-yen3 `_ A py.test plugin providing fixtures and markers to simplify testing of asynchronous tornado applications. Oct 15, 2018 5 - Production/Stable N/A +`pytest-tornasync `_ py.test plugin for testing Python 3.5+ Tornado code Jul 15, 2019 3 - Alpha pytest (>=3.0) +`pytest-track `_ Oct 23, 2020 3 - Alpha pytest (>=3.0) +`pytest-translations `_ Test your translation files. Oct 26, 2020 5 - Production/Stable N/A +`pytest-travis-fold `_ Folds captured output sections in Travis CI build log Nov 29, 2017 4 - Beta pytest (>=2.6.0) +`pytest-trello `_ Plugin for py.test that integrates trello using markers Nov 20, 2015 5 - Production/Stable N/A +`pytest-trepan `_ Pytest plugin for trepan debugger. Jul 28, 2018 5 - Production/Stable N/A +`pytest-trialtemp `_ py.test plugin for using the same _trial_temp working directory as trial Jun 08, 2015 N/A N/A +`pytest-trio `_ Pytest plugin for trio Oct 16, 2020 N/A N/A +`pytest-tspwplib `_ A simple plugin to use with tspwplib Jan 08, 2021 4 - Beta pytest (>=3.5.0) +`pytest-tstcls `_ Test Class Base Mar 23, 2020 5 - Production/Stable N/A +`pytest-twisted `_ A twisted plugin for pytest. Sep 11, 2020 5 - Production/Stable pytest (>=2.3) +`pytest-typhoon-xray `_ Typhoon HIL plugin for pytest Jun 22, 2020 4 - Beta pytest (>=5.4.2) +`pytest-tytest `_ Typhoon HIL plugin for pytest May 25, 2020 4 - Beta pytest (>=5.4.2) +`pytest-ubersmith `_ Easily mock calls to ubersmith at the `requests` level. Apr 13, 2015 N/A N/A +`pytest-ui `_ Text User Interface for running python tests May 03, 2020 4 - Beta pytest +`pytest-unhandled-exception-exit-code `_ Plugin for py.test set a different exit code on uncaught exceptions Jun 22, 2020 5 - Production/Stable pytest (>=2.3) +`pytest-unittest-filter `_ A pytest plugin for filtering unittest-based test classes Jan 12, 2019 4 - Beta pytest (>=3.1.0) +`pytest-unmarked `_ Run only unmarked tests Aug 27, 2019 5 - Production/Stable N/A +`pytest-unordered `_ Test equality of unordered collections in pytest Nov 02, 2020 4 - Beta pytest (>=6.0.0) +`pytest-vagrant `_ A py.test plugin providing access to vagrant. Mar 23, 2020 5 - Production/Stable pytest +`pytest-valgrind `_ Mar 15, 2020 N/A N/A +`pytest-variables `_ pytest plugin for providing variables to tests/fixtures Oct 23, 2019 5 - Production/Stable pytest (>=2.4.2) +`pytest-vcr `_ Plugin for managing VCR.py cassettes Apr 26, 2019 5 - Production/Stable pytest (>=3.6.0) +`pytest-vcrpandas `_ Test from HTTP interactions to dataframe processed. Jan 12, 2019 4 - Beta pytest +`pytest-venv `_ py.test fixture for creating a virtual environment Aug 04, 2020 4 - Beta pytest +`pytest-verbose-parametrize `_ More descriptive output for parametrized py.test tests May 28, 2019 5 - Production/Stable pytest +`pytest-virtualenv `_ Virtualenv fixture for py.test May 28, 2019 5 - Production/Stable pytest +`pytest-voluptuous `_ Pytest plugin for asserting data against voluptuous schema. Jun 09, 2020 N/A pytest +`pytest-vscodedebug `_ A pytest plugin to easily enable debugging tests within Visual Studio Code Dec 04, 2020 4 - Beta N/A +`pytest-vts `_ pytest plugin for automatic recording of http stubbed tests Jun 05, 2019 N/A pytest (>=2.3) +`pytest-vw `_ pytest-vw makes your failing test cases succeed under CI tools scrutiny Oct 07, 2015 4 - Beta N/A +`pytest-vyper `_ Plugin for the vyper smart contract language. May 28, 2020 2 - Pre-Alpha N/A +`pytest-wa-e2e-plugin `_ Pytest plugin for testing whatsapp bots with end to end tests Feb 18, 2020 4 - Beta pytest (>=3.5.0) +`pytest-watch `_ Local continuous test runner with pytest and watchdog. May 20, 2018 N/A N/A +`pytest-wdl `_ Pytest plugin for testing WDL workflows. Nov 17, 2020 5 - Production/Stable N/A +`pytest-webdriver `_ Selenium webdriver fixture for py.test May 28, 2019 5 - Production/Stable pytest +`pytest-wetest `_ Welian API Automation test framework pytest plugin Nov 10, 2018 4 - Beta N/A +`pytest-whirlwind `_ Testing Tornado. Jun 12, 2020 N/A N/A +`pytest-wholenodeid `_ pytest addon for displaying the whole node id for failures Aug 26, 2015 4 - Beta pytest (>=2.0) +`pytest-winnotify `_ Windows tray notifications for py.test results. Apr 22, 2016 N/A N/A +`pytest-workflow `_ A pytest plugin for configuring workflow/pipeline tests using YAML files Dec 14, 2020 5 - Production/Stable pytest (>=5.4.0) +`pytest-xdist `_ pytest xdist plugin for distributed testing and loop-on-failing modes Dec 14, 2020 5 - Production/Stable pytest (>=6.0.0) +`pytest-xdist-debug-for-graingert `_ pytest xdist plugin for distributed testing and loop-on-failing modes Jul 24, 2019 5 - Production/Stable pytest (>=4.4.0) +`pytest-xdist-forked `_ forked from pytest-xdist Feb 10, 2020 5 - Production/Stable pytest (>=4.4.0) +`pytest-xfiles `_ Pytest fixtures providing data read from function, module or package related (x)files. Feb 27, 2018 N/A N/A +`pytest-xlog `_ Extended logging for test and decorators May 31, 2020 4 - Beta N/A +`pytest-xpara `_ An extended parametrizing plugin of pytest. Oct 30, 2017 3 - Alpha pytest +`pytest-xprocess `_ A pytest plugin for managing processes across test runs. Nov 26, 2020 4 - Beta pytest (>=2.8) +`pytest-xray `_ May 30, 2019 3 - Alpha N/A +`pytest-xrayjira `_ Mar 17, 2020 3 - Alpha pytest (==4.3.1) +`pytest-xray-server `_ Nov 29, 2020 3 - Alpha N/A +`pytest-xvfb `_ A pytest plugin to run Xvfb for tests. Jun 09, 2020 4 - Beta pytest (>=2.8.1) +`pytest-yaml `_ This plugin is used to load yaml output to your test using pytest framework. Oct 05, 2018 N/A pytest +`pytest-yamltree `_ Create or check file/directory trees described by YAML Mar 02, 2020 4 - Beta pytest (>=3.1.1) +`pytest-yamlwsgi `_ Run tests against wsgi apps defined in yaml May 11, 2010 N/A N/A +`pytest-yapf `_ Run yapf Jul 06, 2017 4 - Beta pytest (>=3.1.1) +`pytest-yapf3 `_ Validate your Python file format with yapf Aug 03, 2020 5 - Production/Stable pytest (>=5.4) +`pytest-yield `_ PyTest plugin to run tests concurrently, each `yield` switch context to other one Jan 23, 2019 N/A N/A +`pytest-zafira `_ A Zafira plugin for pytest Sep 18, 2019 5 - Production/Stable pytest (==4.1.1) +`pytest-zap `_ OWASP ZAP plugin for py.test. May 12, 2014 4 - Beta N/A +`pytest-zigzag `_ Extend py.test for RPC OpenStack testing. Feb 27, 2019 4 - Beta pytest (~=3.6) +============================================================================================================== ======================================================================================================================================================================== ============== ===================== ============================================ diff --git a/doc/en/plugins.rst b/doc/en/plugins.rst index 855b597392b..8090e7d18a4 100644 --- a/doc/en/plugins.rst +++ b/doc/en/plugins.rst @@ -58,7 +58,7 @@ Here is a little annotated list for some popular plugins: To see a complete list of all plugins with their latest testing status against different pytest and Python versions, please visit -`plugincompat `_. +:doc:`plugin_list`. You may also discover more plugins through a `pytest- pypi.org search`_. diff --git a/doc/en/writing_plugins.rst b/doc/en/writing_plugins.rst index e9806a6664d..92a3dd7dd48 100644 --- a/doc/en/writing_plugins.rst +++ b/doc/en/writing_plugins.rst @@ -122,7 +122,7 @@ you can copy from: * a custom collection example plugin: :ref:`yaml plugin` * builtin plugins which provide pytest's own functionality -* many `external plugins `_ providing additional features +* many :doc:`external plugins ` providing additional features All of these plugins implement :ref:`hooks ` and/or :ref:`fixtures ` to extend and add functionality. diff --git a/scripts/update-plugin-list.py b/scripts/update-plugin-list.py new file mode 100644 index 00000000000..f80e63127ee --- /dev/null +++ b/scripts/update-plugin-list.py @@ -0,0 +1,86 @@ +import datetime +import pathlib +import re + +import packaging.version +import requests +import tabulate + +FILE_HEAD = r"""Plugins List +============ + +PyPI projects that match "pytest-\*" are considered plugins and are listed +automatically. Packages classified as inactive are excluded. +""" +DEVELOPMENT_STATUS_CLASSIFIERS = ( + "Development Status :: 1 - Planning", + "Development Status :: 2 - Pre-Alpha", + "Development Status :: 3 - Alpha", + "Development Status :: 4 - Beta", + "Development Status :: 5 - Production/Stable", + "Development Status :: 6 - Mature", + "Development Status :: 7 - Inactive", +) + + +def iter_plugins(): + regex = r">([\d\w-]*)" + response = requests.get("https://pypi.org/simple") + for match in re.finditer(regex, response.text): + name = match.groups()[0] + if not name.startswith("pytest-"): + continue + response = requests.get(f"https://pypi.org/pypi/{name}/json") + if response.status_code == 404: + # Some packages, like pytest-azurepipelines42, are included in https://pypi.org/simple but + # return 404 on the JSON API. Skip. + continue + response.raise_for_status() + info = response.json()["info"] + if "Development Status :: 7 - Inactive" in info["classifiers"]: + continue + for classifier in DEVELOPMENT_STATUS_CLASSIFIERS: + if classifier in info["classifiers"]: + status = classifier[22:] + break + else: + status = "N/A" + requires = "N/A" + if info["requires_dist"]: + for requirement in info["requires_dist"]: + if requirement == "pytest" or "pytest " in requirement: + requires = requirement + break + releases = response.json()["releases"] + for release in sorted(releases, key=packaging.version.parse, reverse=True): + if releases[release]: + release_date = datetime.date.fromisoformat( + releases[release][-1]["upload_time_iso_8601"].split("T")[0] + ) + last_release = release_date.strftime("%b %d, %Y") + break + name = f'`{info["name"]} <{info["project_url"]}>`_' + summary = info["summary"].replace("\n", "") + summary = re.sub(r"_\b", "", summary) + yield { + "name": name, + "summary": summary, + "last release": last_release, + "status": status, + "requires": requires, + } + + +def main(): + plugins = list(iter_plugins()) + plugin_table = tabulate.tabulate(plugins, headers="keys", tablefmt="rst") + plugin_list = pathlib.Path("doc", "en", "plugin_list.rst") + with plugin_list.open("w") as f: + f.write(FILE_HEAD) + f.write(f"This list contains {len(plugins)} plugins.\n\n") + f.write(plugin_table) + f.write("\n") + + +if __name__ == "__main__": + main() From 07f0eb26b4da671659fefbfcce59d5d56e91f21a Mon Sep 17 00:00:00 2001 From: Hong Xu Date: Fri, 29 Jan 2021 07:54:06 -0800 Subject: [PATCH 0107/2772] Doc: Clarify pytester.run (#8294) Co-authored-by: Bruno Oliveira --- src/_pytest/pytester.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 8ca21d1c538..d428654de88 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -1296,16 +1296,16 @@ def collect_by_name( def popen( self, - cmdargs, + cmdargs: Sequence[Union[str, "os.PathLike[str]"]], stdout: Union[int, TextIO] = subprocess.PIPE, stderr: Union[int, TextIO] = subprocess.PIPE, stdin=CLOSE_STDIN, **kw, ): - """Invoke subprocess.Popen. + """Invoke :py:class:`subprocess.Popen`. - Calls subprocess.Popen making sure the current working directory is - in the PYTHONPATH. + Calls :py:class:`subprocess.Popen` making sure the current working + directory is in the ``PYTHONPATH``. You probably want to use :py:meth:`run` instead. """ @@ -1340,21 +1340,30 @@ def run( ) -> RunResult: """Run a command with arguments. - Run a process using subprocess.Popen saving the stdout and stderr. + Run a process using :py:class:`subprocess.Popen` saving the stdout and + stderr. :param cmdargs: - The sequence of arguments to pass to `subprocess.Popen()`, with path-like objects - being converted to ``str`` automatically. + The sequence of arguments to pass to :py:class:`subprocess.Popen`, + with path-like objects being converted to :py:class:`str` + automatically. :param timeout: The period in seconds after which to timeout and raise :py:class:`Pytester.TimeoutExpired`. :param stdin: - Optional standard input. Bytes are being send, closing - the pipe, otherwise it is passed through to ``popen``. - Defaults to ``CLOSE_STDIN``, which translates to using a pipe - (``subprocess.PIPE``) that gets closed. + Optional standard input. - :rtype: RunResult + - If it is :py:attr:`CLOSE_STDIN` (Default), then this method calls + :py:class:`subprocess.Popen` with ``stdin=subprocess.PIPE``, and + the standard input is closed immediately after the new command is + started. + + - If it is of type :py:class:`bytes`, these bytes are sent to the + standard input of the command. + + - Otherwise, it is passed through to :py:class:`subprocess.Popen`. + For further information in this case, consult the document of the + ``stdin`` parameter in :py:class:`subprocess.Popen`. """ __tracebackhide__ = True From afea19079786761ee167e2bb4e0c90e3f44ba544 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 29 Jan 2021 20:40:43 +0200 Subject: [PATCH 0108/2772] Remove some no longer needed type-ignores --- src/_pytest/cacheprovider.py | 2 +- src/_pytest/monkeypatch.py | 2 +- src/_pytest/pytester.py | 10 +++------- src/_pytest/python.py | 3 +-- src/_pytest/python_api.py | 2 +- .../example_scripts/unittest/test_unittest_asyncio.py | 2 +- testing/test_assertrewrite.py | 4 ++-- 7 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py index 480319c03b4..585cebf6c9d 100755 --- a/src/_pytest/cacheprovider.py +++ b/src/_pytest/cacheprovider.py @@ -415,7 +415,7 @@ def pytest_collection_modifyitems( 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] + return sorted(items, key=lambda item: item.fspath.mtime(), reverse=True) def pytest_sessionfinish(self) -> None: config = self.config diff --git a/src/_pytest/monkeypatch.py b/src/_pytest/monkeypatch.py index ffef87173a8..2c432065625 100644 --- a/src/_pytest/monkeypatch.py +++ b/src/_pytest/monkeypatch.py @@ -91,7 +91,7 @@ def annotated_getattr(obj: object, name: str, ann: str) -> object: def derive_importpath(import_path: str, raising: bool) -> Tuple[str, object]: - if not isinstance(import_path, str) or "." not in import_path: # type: ignore[unreachable] + if not isinstance(import_path, str) or "." not in import_path: raise TypeError(f"must be absolute import path string, not {import_path!r}") module, attr = import_path.rsplit(".", 1) target = resolve(module) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index d428654de88..4fe6e288b43 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -1096,7 +1096,7 @@ def pytest_configure(x, config: Config) -> None: class reprec: # type: ignore pass - reprec.ret = ret # type: ignore + reprec.ret = ret # Typically we reraise keyboard interrupts from the child run # because it's our user requesting interruption of the testing. @@ -1263,9 +1263,7 @@ def getmodulecol( Whether to also write an ``__init__.py`` file to the same directory to ensure it is a package. """ - # TODO: Remove type ignore in next mypy release (> 0.790). - # https://github.com/python/typeshed/pull/4582 - if isinstance(source, os.PathLike): # type: ignore[misc] + if isinstance(source, os.PathLike): path = self.path.joinpath(source) assert not withinit, "not supported for paths" else: @@ -1367,10 +1365,8 @@ def run( """ __tracebackhide__ = True - # TODO: Remove type ignore in next mypy release. - # https://github.com/python/typeshed/pull/4582 cmdargs = tuple( - os.fspath(arg) if isinstance(arg, os.PathLike) else arg for arg in cmdargs # type: ignore[misc] + os.fspath(arg) if isinstance(arg, os.PathLike) else arg for arg in cmdargs ) p1 = self.path.joinpath("stdout") p2 = self.path.joinpath("stderr") diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 29ebd176bbb..3d903ff9b0b 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -139,8 +139,7 @@ def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: def pytest_generate_tests(metafunc: "Metafunc") -> None: for marker in metafunc.definition.iter_markers(name="parametrize"): - # TODO: Fix this type-ignore (overlapping kwargs). - metafunc.parametrize(*marker.args, **marker.kwargs, _param_mark=marker) # type: ignore[misc] + metafunc.parametrize(*marker.args, **marker.kwargs, _param_mark=marker) def pytest_configure(config: Config) -> None: diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index 81ce4f89539..7e0c86479d4 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -713,7 +713,7 @@ def raises( else: excepted_exceptions = expected_exception for exc in excepted_exceptions: - if not isinstance(exc, type) or not issubclass(exc, BaseException): # type: ignore[unreachable] + if not isinstance(exc, type) or not issubclass(exc, BaseException): msg = "expected exception must be a BaseException type, not {}" # type: ignore[unreachable] not_a = exc.__name__ if isinstance(exc, type) else type(exc).__name__ raise TypeError(msg.format(not_a)) diff --git a/testing/example_scripts/unittest/test_unittest_asyncio.py b/testing/example_scripts/unittest/test_unittest_asyncio.py index bbc752de5c1..1cd2168604c 100644 --- a/testing/example_scripts/unittest/test_unittest_asyncio.py +++ b/testing/example_scripts/unittest/test_unittest_asyncio.py @@ -1,5 +1,5 @@ from typing import List -from unittest import IsolatedAsyncioTestCase # type: ignore +from unittest import IsolatedAsyncioTestCase teardowns: List[None] = [] diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index ffe18260f90..cba03406e86 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -381,7 +381,7 @@ def f6() -> None: ) def f7() -> None: - assert False or x() # type: ignore[unreachable] + assert False or x() assert ( getmsg(f7, {"x": x}) @@ -471,7 +471,7 @@ def f1() -> None: assert getmsg(f1) == "assert ((3 % 2) and False)" def f2() -> None: - assert False or 4 % 2 # type: ignore[unreachable] + assert False or 4 % 2 assert getmsg(f2) == "assert (False or (4 % 2))" From f0f19aa8d960dc71eaa00a0f01d3459a9626bbea Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 29 Jan 2021 21:08:37 +0100 Subject: [PATCH 0109/2772] doc: Point out two-argument form of monkeypatch.setattr See the "for convenience" part here: https://docs.pytest.org/en/stable/reference.html#pytest.MonkeyPatch.setattr --- doc/en/monkeypatch.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/en/monkeypatch.rst b/doc/en/monkeypatch.rst index 9480f008f7c..4964ee54815 100644 --- a/doc/en/monkeypatch.rst +++ b/doc/en/monkeypatch.rst @@ -16,6 +16,7 @@ functionality in tests: .. code-block:: python monkeypatch.setattr(obj, name, value, raising=True) + monkeypatch.setattr("somemodule.obj.name", value, raising=True) monkeypatch.delattr(obj, name, raising=True) monkeypatch.setitem(mapping, name, value) monkeypatch.delitem(obj, name, raising=True) From de06f468ed2bb64864f68e05a219e4a0f3986428 Mon Sep 17 00:00:00 2001 From: bluetech Date: Sat, 30 Jan 2021 00:30:28 +0000 Subject: [PATCH 0110/2772] [automated] Update plugin list --- doc/en/plugin_list.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/en/plugin_list.rst b/doc/en/plugin_list.rst index 927838ac942..a4ab7df46bc 100644 --- a/doc/en/plugin_list.rst +++ b/doc/en/plugin_list.rst @@ -3,7 +3,7 @@ Plugins List PyPI projects that match "pytest-\*" are considered plugins and are listed automatically. Packages classified as inactive are excluded. -This list contains 820 plugins. +This list contains 821 plugins. ============================================================================================================== ======================================================================================================================================================================== ============== ===================== ============================================ name summary last release status requires @@ -546,6 +546,7 @@ name `pytest-poo `_ Visualize your crappy tests Jul 14, 2013 5 - Production/Stable N/A `pytest-poo-fail `_ Visualize your failed tests with poo Feb 12, 2015 5 - Production/Stable N/A `pytest-pop `_ A pytest plugin to help with testing pop projects Aug 13, 2020 5 - Production/Stable pytest (>=5.4.0) +`pytest-portion `_ Select a portion of the collected tests Jan 28, 2021 4 - Beta pytest (>=3.5.0) `pytest-postgres `_ Run PostgreSQL in Docker container in Pytest. Mar 22, 2020 N/A pytest `pytest-postgresql `_ Postgresql fixtures and fixture factories for Pytest. Oct 29, 2020 5 - Production/Stable pytest (>=3.0.0) `pytest-power `_ pytest plugin with powerful fixtures Dec 31, 2020 N/A pytest (>=5.4) @@ -633,7 +634,7 @@ name `pytest-rotest `_ Pytest integration with rotest Sep 08, 2019 N/A pytest (>=3.5.0) `pytest-rpc `_ Extend py.test for RPC OpenStack testing. Feb 22, 2019 4 - Beta pytest (~=3.6) `pytest-rt `_ pytest data collector plugin for Testgr Jan 24, 2021 N/A N/A -`pytest-rts `_ Coverage-based regression test selection (RTS) plugin for pytest Dec 21, 2020 N/A pytest +`pytest-rts `_ Coverage-based regression test selection (RTS) plugin for pytest Jan 29, 2021 N/A pytest `pytest-runfailed `_ implement a --failed option for pytest Mar 24, 2016 N/A N/A `pytest-runner `_ Invoke py.test as distutils command with dependency resolution Oct 26, 2019 5 - Production/Stable pytest (!=3.7.3,>=3.5) ; extra == 'testing' `pytest-salt `_ Pytest Salt Plugin Jan 27, 2020 4 - Beta N/A @@ -644,13 +645,13 @@ name `pytest-sanic `_ a pytest plugin for Sanic Sep 24, 2020 N/A pytest (>=5.2) `pytest-sanity `_ Dec 07, 2020 N/A N/A `pytest-sa-pg `_ May 14, 2019 N/A N/A -`pytest-sbase `_ A complete web automation framework for end-to-end testing. Jan 27, 2021 5 - Production/Stable N/A +`pytest-sbase `_ A complete web automation framework for end-to-end testing. Jan 29, 2021 5 - Production/Stable N/A `pytest-scenario `_ pytest plugin for test scenarios Feb 06, 2017 3 - Alpha N/A `pytest-schema `_ 👍 Validate return values against a schema-like object in testing Aug 31, 2020 5 - Production/Stable pytest (>=3.5.0) `pytest-securestore `_ An encrypted password store for use within pytest cases Jun 19, 2019 4 - Beta N/A `pytest-select `_ A pytest plugin which allows to (de-)select tests from a file. Jan 18, 2019 3 - Alpha pytest (>=3.0) `pytest-selenium `_ pytest plugin for Selenium Sep 19, 2020 5 - Production/Stable pytest (>=5.0.0) -`pytest-seleniumbase `_ A complete web automation framework for end-to-end testing. Jan 27, 2021 5 - Production/Stable N/A +`pytest-seleniumbase `_ A complete web automation framework for end-to-end testing. Jan 29, 2021 5 - Production/Stable N/A `pytest-selenium-enhancer `_ pytest plugin for Selenium Nov 26, 2020 5 - Production/Stable N/A `pytest-selenium-pdiff `_ A pytest package implementing perceptualdiff for Selenium tests. Apr 06, 2017 2 - Pre-Alpha N/A `pytest-send-email `_ Send pytest execution result email Dec 04, 2019 N/A N/A From c971f2f474df929dd06b5c4dae27514c7fb47e70 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 30 Jan 2021 13:01:41 +0200 Subject: [PATCH 0111/2772] ci: give pytest bot the credit for updating the plugin list --- .github/workflows/update-plugin-list.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/update-plugin-list.yml b/.github/workflows/update-plugin-list.yml index 10b5cb99478..3f4ec092bd9 100644 --- a/.github/workflows/update-plugin-list.yml +++ b/.github/workflows/update-plugin-list.yml @@ -26,6 +26,7 @@ jobs: uses: peter-evans/create-pull-request@2455e1596942c2902952003bbb574afbbe2ab2e6 with: commit-message: '[automated] Update plugin list' + author: 'pytest bot ' branch: update-plugin-list/patch delete-branch: true branch-suffix: short-commit-hash From 5c61d60b0d9328889799b8cf698841b61ff7035b Mon Sep 17 00:00:00 2001 From: pytest bot Date: Mon, 1 Feb 2021 00:32:44 +0000 Subject: [PATCH 0112/2772] [automated] Update plugin list --- doc/en/plugin_list.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/en/plugin_list.rst b/doc/en/plugin_list.rst index a4ab7df46bc..9c6b6f0aa41 100644 --- a/doc/en/plugin_list.rst +++ b/doc/en/plugin_list.rst @@ -34,7 +34,7 @@ name `pytest-apistellar `_ apistellar plugin for pytest. Jun 18, 2019 N/A N/A `pytest-appengine `_ AppEngine integration that works well with pytest-django Feb 27, 2017 N/A N/A `pytest-appium `_ Pytest plugin for appium Dec 05, 2019 N/A N/A -`pytest-approvaltests `_ A plugin to use approvaltests with pytest Aug 02, 2019 4 - Beta N/A +`pytest-approvaltests `_ A plugin to use approvaltests with pytest Jan 31, 2021 4 - Beta pytest (>=3.5.0) `pytest-arraydiff `_ pytest plugin to help with comparing array output from tests Dec 06, 2018 4 - Beta pytest `pytest-asgi-server `_ Convenient ASGI client/server fixtures for Pytest Dec 12, 2020 N/A pytest (>=5.4.1) `pytest-asptest `_ test Answer Set Programming programs Apr 28, 2018 4 - Beta N/A @@ -123,7 +123,7 @@ name `pytest-codestyle `_ pytest plugin to run pycodestyle Mar 23, 2020 3 - Alpha N/A `pytest-collect-formatter `_ Formatter for pytest collect output Nov 19, 2020 5 - Production/Stable N/A `pytest-colordots `_ Colorizes the progress indicators Oct 06, 2017 5 - Production/Stable N/A -`pytest-commander `_ An interactive GUI test runner for PyTest Nov 21, 2020 N/A pytest (>=5.0.0) +`pytest-commander `_ An interactive GUI test runner for PyTest Jan 31, 2021 N/A pytest (>=5.0.0) `pytest-common-subject `_ pytest framework for testing different aspects of a common method Nov 12, 2020 N/A pytest (>=3.6,<7) `pytest-concurrent `_ Concurrently execute test cases with multithread, multiprocess and gevent Jan 12, 2019 4 - Beta pytest (>=3.1.1) `pytest-config `_ Base configurations and utilities for developing your Python project test suite with pytest. Nov 07, 2014 5 - Production/Stable N/A @@ -339,7 +339,7 @@ name `pytest-homeassistant `_ A pytest plugin for use with homeassistant custom components. Aug 12, 2020 4 - Beta N/A `pytest-homeassistant-custom-component `_ Experimental package to automatically extract test plugins for Home Assistant custom components Jan 05, 2021 3 - Alpha pytest (==6.1.2) `pytest-honors `_ Report on tests that honor constraints, and guard against regressions Mar 06, 2020 4 - Beta N/A -`pytest-hoverfly-wrapper `_ Integrates the Hoverfly HTTP proxy into Pytest Oct 25, 2020 4 - Beta N/A +`pytest-hoverfly-wrapper `_ Integrates the Hoverfly HTTP proxy into Pytest Jan 31, 2021 4 - Beta N/A `pytest-html `_ pytest plugin for generating HTML reports Dec 13, 2020 5 - Production/Stable pytest (!=6.0.0,>=5.0) `pytest-html-lee `_ optimized pytest plugin for generating HTML reports Jun 30, 2020 5 - Production/Stable pytest (>=5.0) `pytest-html-profiling `_ Pytest plugin for generating HTML reports with per-test profiling and optionally call graph visualizations. Based on pytest-html by Dave Hunt. Feb 11, 2020 5 - Production/Stable pytest (>=3.0) @@ -548,7 +548,7 @@ name `pytest-pop `_ A pytest plugin to help with testing pop projects Aug 13, 2020 5 - Production/Stable pytest (>=5.4.0) `pytest-portion `_ Select a portion of the collected tests Jan 28, 2021 4 - Beta pytest (>=3.5.0) `pytest-postgres `_ Run PostgreSQL in Docker container in Pytest. Mar 22, 2020 N/A pytest -`pytest-postgresql `_ Postgresql fixtures and fixture factories for Pytest. Oct 29, 2020 5 - Production/Stable pytest (>=3.0.0) +`pytest-postgresql `_ Postgresql fixtures and fixture factories for Pytest. Jan 31, 2021 5 - Production/Stable pytest (>=3.0.0) `pytest-power `_ pytest plugin with powerful fixtures Dec 31, 2020 N/A pytest (>=5.4) `pytest-pride `_ Minitest-style test colors Apr 02, 2016 3 - Alpha N/A `pytest-print `_ pytest-print adds the printer fixture you can use to print messages to the user (directly to the pytest runner, not stdout) Oct 23, 2020 5 - Production/Stable pytest (>=3.0.0) @@ -645,13 +645,13 @@ name `pytest-sanic `_ a pytest plugin for Sanic Sep 24, 2020 N/A pytest (>=5.2) `pytest-sanity `_ Dec 07, 2020 N/A N/A `pytest-sa-pg `_ May 14, 2019 N/A N/A -`pytest-sbase `_ A complete web automation framework for end-to-end testing. Jan 29, 2021 5 - Production/Stable N/A +`pytest-sbase `_ A complete web automation framework for end-to-end testing. Jan 31, 2021 5 - Production/Stable N/A `pytest-scenario `_ pytest plugin for test scenarios Feb 06, 2017 3 - Alpha N/A `pytest-schema `_ 👍 Validate return values against a schema-like object in testing Aug 31, 2020 5 - Production/Stable pytest (>=3.5.0) `pytest-securestore `_ An encrypted password store for use within pytest cases Jun 19, 2019 4 - Beta N/A `pytest-select `_ A pytest plugin which allows to (de-)select tests from a file. Jan 18, 2019 3 - Alpha pytest (>=3.0) `pytest-selenium `_ pytest plugin for Selenium Sep 19, 2020 5 - Production/Stable pytest (>=5.0.0) -`pytest-seleniumbase `_ A complete web automation framework for end-to-end testing. Jan 29, 2021 5 - Production/Stable N/A +`pytest-seleniumbase `_ A complete web automation framework for end-to-end testing. Jan 31, 2021 5 - Production/Stable N/A `pytest-selenium-enhancer `_ pytest plugin for Selenium Nov 26, 2020 5 - Production/Stable N/A `pytest-selenium-pdiff `_ A pytest package implementing perceptualdiff for Selenium tests. Apr 06, 2017 2 - Pre-Alpha N/A `pytest-send-email `_ Send pytest execution result email Dec 04, 2019 N/A N/A From 1b2ca22e9bf6720445ca8eab928b44cd27b46c8f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 Feb 2021 16:49:41 +0000 Subject: [PATCH 0113/2772] [pre-commit.ci] pre-commit autoupdate --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9130a79a06b..2eed21fc883 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,7 @@ repos: - id: black args: [--safe, --quiet] - repo: https://github.com/asottile/blacken-docs - rev: v1.9.1 + rev: v1.9.2 hooks: - id: blacken-docs additional_dependencies: [black==20.8b1] @@ -34,7 +34,7 @@ repos: - id: reorder-python-imports args: ['--application-directories=.:src', --py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.7.4 + rev: v2.9.0 hooks: - id: pyupgrade args: [--py36-plus] From 3165b1e3238df1424431f754182c66fd6d18bee4 Mon Sep 17 00:00:00 2001 From: pytest bot Date: Tue, 2 Feb 2021 00:31:11 +0000 Subject: [PATCH 0114/2772] [automated] Update plugin list --- doc/en/plugin_list.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/en/plugin_list.rst b/doc/en/plugin_list.rst index a4ab7df46bc..8747bf75e8a 100644 --- a/doc/en/plugin_list.rst +++ b/doc/en/plugin_list.rst @@ -34,7 +34,7 @@ name `pytest-apistellar `_ apistellar plugin for pytest. Jun 18, 2019 N/A N/A `pytest-appengine `_ AppEngine integration that works well with pytest-django Feb 27, 2017 N/A N/A `pytest-appium `_ Pytest plugin for appium Dec 05, 2019 N/A N/A -`pytest-approvaltests `_ A plugin to use approvaltests with pytest Aug 02, 2019 4 - Beta N/A +`pytest-approvaltests `_ A plugin to use approvaltests with pytest Jan 31, 2021 4 - Beta pytest (>=3.5.0) `pytest-arraydiff `_ pytest plugin to help with comparing array output from tests Dec 06, 2018 4 - Beta pytest `pytest-asgi-server `_ Convenient ASGI client/server fixtures for Pytest Dec 12, 2020 N/A pytest (>=5.4.1) `pytest-asptest `_ test Answer Set Programming programs Apr 28, 2018 4 - Beta N/A @@ -123,7 +123,7 @@ name `pytest-codestyle `_ pytest plugin to run pycodestyle Mar 23, 2020 3 - Alpha N/A `pytest-collect-formatter `_ Formatter for pytest collect output Nov 19, 2020 5 - Production/Stable N/A `pytest-colordots `_ Colorizes the progress indicators Oct 06, 2017 5 - Production/Stable N/A -`pytest-commander `_ An interactive GUI test runner for PyTest Nov 21, 2020 N/A pytest (>=5.0.0) +`pytest-commander `_ An interactive GUI test runner for PyTest Jan 31, 2021 N/A pytest (>=5.0.0) `pytest-common-subject `_ pytest framework for testing different aspects of a common method Nov 12, 2020 N/A pytest (>=3.6,<7) `pytest-concurrent `_ Concurrently execute test cases with multithread, multiprocess and gevent Jan 12, 2019 4 - Beta pytest (>=3.1.1) `pytest-config `_ Base configurations and utilities for developing your Python project test suite with pytest. Nov 07, 2014 5 - Production/Stable N/A @@ -337,9 +337,9 @@ name `pytest-historic `_ Custom report to display pytest historical execution records Apr 08, 2020 N/A pytest `pytest-historic-hook `_ Custom listener to store execution results into MYSQL DB, which is used for pytest-historic report Apr 08, 2020 N/A pytest `pytest-homeassistant `_ A pytest plugin for use with homeassistant custom components. Aug 12, 2020 4 - Beta N/A -`pytest-homeassistant-custom-component `_ Experimental package to automatically extract test plugins for Home Assistant custom components Jan 05, 2021 3 - Alpha pytest (==6.1.2) +`pytest-homeassistant-custom-component `_ Experimental package to automatically extract test plugins for Home Assistant custom components Feb 01, 2021 3 - Alpha pytest (==6.2.2) `pytest-honors `_ Report on tests that honor constraints, and guard against regressions Mar 06, 2020 4 - Beta N/A -`pytest-hoverfly-wrapper `_ Integrates the Hoverfly HTTP proxy into Pytest Oct 25, 2020 4 - Beta N/A +`pytest-hoverfly-wrapper `_ Integrates the Hoverfly HTTP proxy into Pytest Jan 31, 2021 4 - Beta N/A `pytest-html `_ pytest plugin for generating HTML reports Dec 13, 2020 5 - Production/Stable pytest (!=6.0.0,>=5.0) `pytest-html-lee `_ optimized pytest plugin for generating HTML reports Jun 30, 2020 5 - Production/Stable pytest (>=5.0) `pytest-html-profiling `_ Pytest plugin for generating HTML reports with per-test profiling and optionally call graph visualizations. Based on pytest-html by Dave Hunt. Feb 11, 2020 5 - Production/Stable pytest (>=3.0) @@ -548,7 +548,7 @@ name `pytest-pop `_ A pytest plugin to help with testing pop projects Aug 13, 2020 5 - Production/Stable pytest (>=5.4.0) `pytest-portion `_ Select a portion of the collected tests Jan 28, 2021 4 - Beta pytest (>=3.5.0) `pytest-postgres `_ Run PostgreSQL in Docker container in Pytest. Mar 22, 2020 N/A pytest -`pytest-postgresql `_ Postgresql fixtures and fixture factories for Pytest. Oct 29, 2020 5 - Production/Stable pytest (>=3.0.0) +`pytest-postgresql `_ Postgresql fixtures and fixture factories for Pytest. Jan 31, 2021 5 - Production/Stable pytest (>=3.0.0) `pytest-power `_ pytest plugin with powerful fixtures Dec 31, 2020 N/A pytest (>=5.4) `pytest-pride `_ Minitest-style test colors Apr 02, 2016 3 - Alpha N/A `pytest-print `_ pytest-print adds the printer fixture you can use to print messages to the user (directly to the pytest runner, not stdout) Oct 23, 2020 5 - Production/Stable pytest (>=3.0.0) @@ -645,13 +645,13 @@ name `pytest-sanic `_ a pytest plugin for Sanic Sep 24, 2020 N/A pytest (>=5.2) `pytest-sanity `_ Dec 07, 2020 N/A N/A `pytest-sa-pg `_ May 14, 2019 N/A N/A -`pytest-sbase `_ A complete web automation framework for end-to-end testing. Jan 29, 2021 5 - Production/Stable N/A +`pytest-sbase `_ A complete web automation framework for end-to-end testing. Jan 31, 2021 5 - Production/Stable N/A `pytest-scenario `_ pytest plugin for test scenarios Feb 06, 2017 3 - Alpha N/A `pytest-schema `_ 👍 Validate return values against a schema-like object in testing Aug 31, 2020 5 - Production/Stable pytest (>=3.5.0) `pytest-securestore `_ An encrypted password store for use within pytest cases Jun 19, 2019 4 - Beta N/A `pytest-select `_ A pytest plugin which allows to (de-)select tests from a file. Jan 18, 2019 3 - Alpha pytest (>=3.0) `pytest-selenium `_ pytest plugin for Selenium Sep 19, 2020 5 - Production/Stable pytest (>=5.0.0) -`pytest-seleniumbase `_ A complete web automation framework for end-to-end testing. Jan 29, 2021 5 - Production/Stable N/A +`pytest-seleniumbase `_ A complete web automation framework for end-to-end testing. Jan 31, 2021 5 - Production/Stable N/A `pytest-selenium-enhancer `_ pytest plugin for Selenium Nov 26, 2020 5 - Production/Stable N/A `pytest-selenium-pdiff `_ A pytest package implementing perceptualdiff for Selenium tests. Apr 06, 2017 2 - Pre-Alpha N/A `pytest-send-email `_ Send pytest execution result email Dec 04, 2019 N/A N/A @@ -695,7 +695,7 @@ name `pytest-split `_ Pytest plugin for splitting test suite based on test execution time Apr 07, 2020 1 - Planning N/A `pytest-splitio `_ Split.io SDK integration for e2e tests Sep 22, 2020 N/A pytest (<7,>=5.0) `pytest-split-tests `_ A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. Forked from Mark Adams' original project pytest-test-groups. May 28, 2019 N/A pytest (>=2.5) -`pytest-splunk-addon `_ A Dynamic test tool for Splunk Apps and Add-ons Jan 05, 2021 N/A pytest (>5.4.0,<6.1) +`pytest-splunk-addon `_ A Dynamic test tool for Splunk Apps and Add-ons Feb 01, 2021 N/A pytest (>5.4.0,<6.1) `pytest-splunk-addon-ui-smartx `_ Library to support testing Splunk Add-on UX Jan 18, 2021 N/A N/A `pytest-splunk-env `_ pytest fixtures for interaction with Splunk Enterprise and Splunk Cloud Oct 22, 2020 N/A pytest (>=6.1.1,<7.0.0) `pytest-sqitch `_ sqitch for pytest Apr 06, 2020 4 - Beta N/A From 100c8deab5fccda043b2087bd450d074c1f8756d Mon Sep 17 00:00:00 2001 From: Matthew Hughes Date: Sat, 30 Jan 2021 19:50:56 +0000 Subject: [PATCH 0115/2772] Fix various typos in fixture docs --- doc/en/fixture.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index a629bb7d47f..ca44fbd8263 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -375,7 +375,7 @@ Fixtures are reusable ^^^^^^^^^^^^^^^^^^^^^ One of the things that makes pytest's fixture system so powerful, is that it -gives us the abilty to define a generic setup step that can reused over and +gives us the ability to define a generic setup step that can reused over and over, just like a normal function would be used. Two different tests can request the same fixture and have pytest give each test their own result from that fixture. @@ -902,7 +902,7 @@ attempt to tear them down as it normally would. 2. Adding finalizers directly ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -While yield fixtures are considered to be the cleaner and more straighforward +While yield fixtures are considered to be the cleaner and more straightforward option, there is another choice, and that is to add "finalizer" functions directly to the test's `request-context`_ object. It brings a similar result as yield fixtures, but requires a bit more verbosity. @@ -1026,7 +1026,7 @@ making one state-changing action each, and then bundling them together with their teardown code, as :ref:`the email examples above ` showed. The chance that a state-changing operation can fail but still modify state is -neglibible, as most of these operations tend to be `transaction`_-based (at +negligible, as most of these operations tend to be `transaction`_-based (at least at the level of testing where state could be left behind). So if we make sure that any successful state-changing action gets torn down by moving it to a separate fixture function and separating it from other, potentially failing @@ -1124,7 +1124,7 @@ never have been made. .. _`conftest.py`: .. _`conftest`: -Fixture availabiility +Fixture availability --------------------- Fixture availability is determined from the perspective of the test. A fixture @@ -1410,9 +1410,9 @@ pytest doesn't know where ``c`` should go in the case, so it should be assumed that it could go anywhere between ``g`` and ``b``. This isn't necessarily bad, but it's something to keep in mind. If the order -they execute in could affect the behavior a test is targetting, or could +they execute in could affect the behavior a test is targeting, or could otherwise influence the result of a test, then the order should be defined -explicitely in a way that allows pytest to linearize/"flatten" that order. +explicitly in a way that allows pytest to linearize/"flatten" that order. .. _`autouse order`: @@ -1506,7 +1506,7 @@ of what we've gone over so far. All that's needed is stepping up to a larger scope, then having the **act** step defined as an autouse fixture, and finally, making sure all the fixtures -are targetting that highler level scope. +are targeting that higher level scope. Let's pull :ref:`an example from above `, and tweak it a bit. Let's say that in addition to checking for a welcome message in the header, @@ -1777,7 +1777,7 @@ Parametrizing fixtures ----------------------------------------------------------------- Fixture functions can be parametrized in which case they will be called -multiple times, each time executing the set of dependent tests, i. e. the +multiple times, each time executing the set of dependent tests, i.e. the tests that depend on this fixture. Test functions usually do not need to be aware of their re-running. Fixture parametrization helps to write exhaustive functional tests for components which themselves can be From 298541f540a0b566002c8e6a01e7e093c6750158 Mon Sep 17 00:00:00 2001 From: Matthew Hughes Date: Sat, 30 Jan 2021 22:14:35 +0000 Subject: [PATCH 0116/2772] Add basic emaillib for tests in fixture.rst This will be used to power regendoc runs for later tests --- doc/en/fixture.rst | 42 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index ca44fbd8263..38636ff7514 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -844,12 +844,42 @@ Once the test is finished, pytest will go back down the list of fixtures, but in the *reverse order*, taking each one that yielded, and running the code inside it that was *after* the ``yield`` statement. -As a simple example, let's say we want to test sending email from one user to -another. We'll have to first make each user, then send the email from one user -to the other, and finally assert that the other user received that message in -their inbox. If we want to clean up after the test runs, we'll likely have to -make sure the other user's mailbox is emptied before deleting that user, -otherwise the system may complain. +As a simple example, consider this basic email module: + +.. code-block:: python + + # content of emaillib.py + class MailAdminClient: + def create_user(self): + return MailUser() + + def delete_user(self, user): + # do some cleanup + pass + + + class MailUser: + def __init__(self): + self.inbox = [] + + def send_email(self, email, other): + other.inbox.append(email) + + def clear_mailbox(self): + self.mailbox.clear() + + + class Email: + def __init__(self, subject, body): + self.body = body + self.subject = subject + +Let's say we want to test sending email from one user to another. We'll have to +first make each user, then send the email from one user to the other, and +finally assert that the other user received that message in their inbox. If we +want to clean up after the test runs, we'll likely have to make sure the other +user's mailbox is emptied before deleting that user, otherwise the system may +complain. Here's what that might look like: From 1d895dd46cba716ae96ceb5688d52bdff5445168 Mon Sep 17 00:00:00 2001 From: pytest bot Date: Thu, 4 Feb 2021 00:26:06 +0000 Subject: [PATCH 0117/2772] [automated] Update plugin list --- doc/en/plugin_list.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/plugin_list.rst b/doc/en/plugin_list.rst index 8747bf75e8a..6aae603a8a7 100644 --- a/doc/en/plugin_list.rst +++ b/doc/en/plugin_list.rst @@ -212,7 +212,7 @@ name `pytest-docker-pexpect `_ pytest plugin for writing functional tests with pexpect and docker Jan 14, 2019 N/A pytest `pytest-docker-postgresql `_ A simple plugin to use with pytest Sep 24, 2019 4 - Beta pytest (>=3.5.0) `pytest-docker-py `_ Easy to use, simple to extend, pytest plugin that minimally leverages docker-py. Nov 27, 2018 N/A pytest (==4.0.0) -`pytest-docker-registry-fixtures `_ Pytest fixtures for testing with docker registries. Jan 25, 2021 4 - Beta pytest +`pytest-docker-registry-fixtures `_ Pytest fixtures for testing with docker registries. Feb 03, 2021 4 - Beta pytest `pytest-docker-tools `_ Docker integration tests for pytest Jan 15, 2021 4 - Beta pytest (>=6.0.1,<7.0.0) `pytest-docs `_ Documentation tool for pytest Nov 11, 2018 4 - Beta pytest (>=3.5.0) `pytest-docstyle `_ pytest plugin to run pydocstyle Mar 23, 2020 3 - Alpha N/A From b77f071befd67492776cefb632341639e98e30b6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 4 Feb 2021 11:40:28 +0100 Subject: [PATCH 0118/2772] doc: Remove past training --- doc/en/index.rst | 6 ------ 1 file changed, 6 deletions(-) diff --git a/doc/en/index.rst b/doc/en/index.rst index f74ef90a785..0361805d95a 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -1,11 +1,5 @@ :orphan: -.. sidebar:: Next Open Trainings - - - `Professional testing with Python `_, via Python Academy, February 1-3 2021, remote and Leipzig (Germany). **Early-bird discount available until January 15th**. - - Also see `previous talks and blogposts `_. - .. _features: pytest: helps you write better programs From 97cfd66806913250b3b26775da66ff75f6673b29 Mon Sep 17 00:00:00 2001 From: Matthew Hughes Date: Sat, 30 Jan 2021 22:23:04 +0000 Subject: [PATCH 0119/2772] Add regendoc runs for emaillib tests in fixture Also update these tests ensure they pass, and be explicit about the test file called in an existing test to avoid unintentional calls to the added tests --- doc/en/fixture.rst | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index 38636ff7514..de1591d2d61 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -829,6 +829,8 @@ This system can be leveraged in two ways. 1. ``yield`` fixtures (recommended) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. regendoc: wipe + "Yield" fixtures ``yield`` instead of ``return``. With these fixtures, we can run some code and pass an object back to the requesting fixture/test, just like with the other fixtures. The only differences are: @@ -866,13 +868,13 @@ As a simple example, consider this basic email module: other.inbox.append(email) def clear_mailbox(self): - self.mailbox.clear() + self.inbox.clear() class Email: def __init__(self, subject, body): - self.body = body self.subject = subject + self.body = body Let's say we want to test sending email from one user to another. We'll have to first make each user, then send the email from one user to the other, and @@ -885,6 +887,7 @@ Here's what that might look like: .. code-block:: python + # content of test_emaillib.py import pytest from emaillib import Email, MailAdminClient @@ -899,17 +902,17 @@ Here's what that might look like: def sending_user(mail_admin): user = mail_admin.create_user() yield user - admin_client.delete_user(user) + mail_admin.delete_user(user) @pytest.fixture def receiving_user(mail_admin): user = mail_admin.create_user() yield user - admin_client.delete_user(user) + mail_admin.delete_user(user) - def test_email_received(sending_user, receiving_user, email): + def test_email_received(sending_user, receiving_user): email = Email(subject="Hey!", body="How's it going?") sending_user.send_email(email, receiving_user) assert email in receiving_user.inbox @@ -921,6 +924,10 @@ There is a risk that even having the order right on the teardown side of things doesn't guarantee a safe cleanup. That's covered in a bit more detail in :ref:`safe teardowns`. +.. code-block:: pytest + + $ pytest -q test_emaillib.py + Handling errors for yield fixture """"""""""""""""""""""""""""""""" @@ -952,6 +959,7 @@ Here's how the previous example would look using the ``addfinalizer`` method: .. code-block:: python + # content of test_emaillib.py import pytest from emaillib import Email, MailAdminClient @@ -966,7 +974,7 @@ Here's how the previous example would look using the ``addfinalizer`` method: def sending_user(mail_admin): user = mail_admin.create_user() yield user - admin_client.delete_user(user) + mail_admin.delete_user(user) @pytest.fixture @@ -974,7 +982,7 @@ Here's how the previous example would look using the ``addfinalizer`` method: user = mail_admin.create_user() def delete_user(): - admin_client.delete_user(user) + mail_admin.delete_user(user) request.addfinalizer(delete_user) return user @@ -986,7 +994,7 @@ Here's how the previous example would look using the ``addfinalizer`` method: sending_user.send_email(_email, receiving_user) def empty_mailbox(): - receiving_user.delete_email(_email) + receiving_user.clear_mailbox() request.addfinalizer(empty_mailbox) return _email @@ -999,6 +1007,10 @@ Here's how the previous example would look using the ``addfinalizer`` method: It's a bit longer than yield fixtures and a bit more complex, but it does offer some nuances for when you're in a pinch. +.. code-block:: pytest + + $ pytest -q test_emaillib.py + .. _`safe teardowns`: Safe teardowns @@ -1014,6 +1026,7 @@ above): .. code-block:: python + # content of test_emaillib.py import pytest from emaillib import Email, MailAdminClient @@ -1025,11 +1038,11 @@ above): sending_user = mail_admin.create_user() receiving_user = mail_admin.create_user() email = Email(subject="Hey!", body="How's it going?") - sending_user.send_emai(email, receiving_user) + sending_user.send_email(email, receiving_user) yield receiving_user, email - receiving_user.delete_email(email) - admin_client.delete_user(sending_user) - admin_client.delete_user(receiving_user) + receiving_user.clear_mailbox() + mail_admin.delete_user(sending_user) + mail_admin.delete_user(receiving_user) def test_email_received(setup): @@ -1046,6 +1059,10 @@ One option might be to go with the ``addfinalizer`` method instead of yield fixtures, but that might get pretty complex and difficult to maintain (and it wouldn't be compact anymore). +.. code-block:: pytest + + $ pytest -q test_emaillib.py + .. _`safe fixture structure`: Safe fixture structure @@ -1676,7 +1693,7 @@ again, nothing much has changed: .. code-block:: pytest - $ pytest -s -q --tb=no + $ pytest -s -q --tb=no test_module.py FFfinalizing (smtp.gmail.com) ========================= short test summary info ========================== From 709c211e68bf3a8aa452b61056b5294a21050dfb Mon Sep 17 00:00:00 2001 From: Matthew Hughes Date: Thu, 4 Feb 2021 18:00:17 +0000 Subject: [PATCH 0120/2772] Run regendoc over fixture docs This is the result of running: $ cd doc/en && make regen REGENDOC_FILES=fixture.rst --- doc/en/fixture.rst | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index de1591d2d61..d72a0e619b5 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -927,6 +927,8 @@ doesn't guarantee a safe cleanup. That's covered in a bit more detail in .. code-block:: pytest $ pytest -q test_emaillib.py + . [100%] + 1 passed in 0.12s Handling errors for yield fixture """"""""""""""""""""""""""""""""" @@ -1010,6 +1012,8 @@ does offer some nuances for when you're in a pinch. .. code-block:: pytest $ pytest -q test_emaillib.py + . [100%] + 1 passed in 0.12s .. _`safe teardowns`: @@ -1062,6 +1066,8 @@ wouldn't be compact anymore). .. code-block:: pytest $ pytest -q test_emaillib.py + . [100%] + 1 passed in 0.12s .. _`safe fixture structure`: @@ -1978,11 +1984,13 @@ Running the above tests results in the following test IDs being used: platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR - collected 10 items + collected 11 items + + @@ -1994,7 +2002,7 @@ Running the above tests results in the following test IDs being used: - ======================= 10 tests collected in 0.12s ======================== + ======================= 11 tests collected in 0.12s ======================== .. _`fixture-parametrize-marks`: From 80c223474c98fd59a07776994e672e934866c7d5 Mon Sep 17 00:00:00 2001 From: Hong Xu Date: Thu, 4 Feb 2021 13:44:22 -0800 Subject: [PATCH 0121/2772] Type annotation polishing for symbols around Pytester.run (#8298) * Type annotation polishing for symbols around Pytester.run Hopefully these will help document readers understand pertinent methods and constants better. Following up #8294 * Use NOTSET instead of object --- src/_pytest/pytester.py | 42 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 4fe6e288b43..853dfbe9489 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -20,6 +20,7 @@ from typing import Callable from typing import Dict from typing import Generator +from typing import IO from typing import Iterable from typing import List from typing import Optional @@ -41,6 +42,8 @@ from _pytest._code import Source from _pytest.capture import _get_multicapture from _pytest.compat import final +from _pytest.compat import NOTSET +from _pytest.compat import NotSetType from _pytest.config import _PluggyPlugin from _pytest.config import Config from _pytest.config import ExitCode @@ -66,6 +69,7 @@ if TYPE_CHECKING: + from typing_extensions import Final from typing_extensions import Literal import pexpect @@ -651,7 +655,7 @@ class Pytester: __test__ = False - CLOSE_STDIN = object + CLOSE_STDIN: "Final" = NOTSET class TimeoutExpired(Exception): pass @@ -1297,13 +1301,13 @@ def popen( cmdargs: Sequence[Union[str, "os.PathLike[str]"]], stdout: Union[int, TextIO] = subprocess.PIPE, stderr: Union[int, TextIO] = subprocess.PIPE, - stdin=CLOSE_STDIN, + stdin: Union[NotSetType, bytes, IO[Any], int] = CLOSE_STDIN, **kw, ): """Invoke :py:class:`subprocess.Popen`. Calls :py:class:`subprocess.Popen` making sure the current working - directory is in the ``PYTHONPATH``. + directory is in ``PYTHONPATH``. You probably want to use :py:meth:`run` instead. """ @@ -1334,7 +1338,7 @@ def run( self, *cmdargs: Union[str, "os.PathLike[str]"], timeout: Optional[float] = None, - stdin=CLOSE_STDIN, + stdin: Union[NotSetType, bytes, IO[Any], int] = CLOSE_STDIN, ) -> RunResult: """Run a command with arguments. @@ -1426,21 +1430,17 @@ def _dump_lines(self, lines, fp): def _getpytestargs(self) -> Tuple[str, ...]: return sys.executable, "-mpytest" - def runpython(self, script) -> RunResult: - """Run a python script using sys.executable as interpreter. - - :rtype: RunResult - """ + def runpython(self, script: "os.PathLike[str]") -> RunResult: + """Run a python script using sys.executable as interpreter.""" return self.run(sys.executable, script) - def runpython_c(self, command): - """Run python -c "command". - - :rtype: RunResult - """ + def runpython_c(self, command: str) -> RunResult: + """Run ``python -c "command"``.""" return self.run(sys.executable, "-c", command) - def runpytest_subprocess(self, *args, timeout: Optional[float] = None) -> RunResult: + def runpytest_subprocess( + self, *args: Union[str, "os.PathLike[str]"], timeout: Optional[float] = None + ) -> RunResult: """Run pytest as a subprocess with given arguments. Any plugins added to the :py:attr:`plugins` list will be added using the @@ -1454,8 +1454,6 @@ def runpytest_subprocess(self, *args, timeout: Optional[float] = None) -> RunRes :param timeout: The period in seconds after which to timeout and raise :py:class:`Pytester.TimeoutExpired`. - - :rtype: RunResult """ __tracebackhide__ = True p = make_numbered_dir(root=self.path, prefix="runpytest-") @@ -1529,9 +1527,9 @@ class Testdir: __test__ = False - CLOSE_STDIN = Pytester.CLOSE_STDIN - TimeoutExpired = Pytester.TimeoutExpired - Session = Pytester.Session + CLOSE_STDIN: "Final" = Pytester.CLOSE_STDIN + TimeoutExpired: "Final" = Pytester.TimeoutExpired + Session: "Final" = Pytester.Session def __init__(self, pytester: Pytester, *, _ispytest: bool = False) -> None: check_ispytest(_ispytest) @@ -1695,8 +1693,8 @@ def collect_by_name( def popen( self, cmdargs, - stdout: Union[int, TextIO] = subprocess.PIPE, - stderr: Union[int, TextIO] = subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, stdin=CLOSE_STDIN, **kw, ): From d358a060add416e11b0e231cbfe9d97b02335ad0 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Thu, 4 Feb 2021 11:52:13 +0200 Subject: [PATCH 0122/2772] config/argparsing: use proper deprecations instead of ad-hoc DeprecationWarning Proper for removing this in the next major pytest release. --- changelog/8315.deprecation.rst | 5 +++++ doc/en/deprecations.rst | 13 +++++++++++++ src/_pytest/config/argparsing.py | 22 ++++++---------------- src/_pytest/deprecated.py | 19 +++++++++++++++++++ 4 files changed, 43 insertions(+), 16 deletions(-) create mode 100644 changelog/8315.deprecation.rst diff --git a/changelog/8315.deprecation.rst b/changelog/8315.deprecation.rst new file mode 100644 index 00000000000..9b49d7c2f19 --- /dev/null +++ b/changelog/8315.deprecation.rst @@ -0,0 +1,5 @@ +Several behaviors of :meth:`Parser.addoption <_pytest.config.argparsing.Parser.addoption>` are now +scheduled for removal in pytest 7 (deprecated since pytest 2.4.0): + +- ``parser.addoption(..., help=".. %default ..")`` - use ``%(default)s`` instead. +- ``parser.addoption(..., type="int/string/float/complex")`` - use ``type=int`` etc. instead. diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index 0dcbd8ceb36..a3d7fd49a33 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -18,6 +18,19 @@ Deprecated Features Below is a complete list of all pytest features which are considered deprecated. Using those features will issue :class:`PytestWarning` or subclasses, which can be filtered using :ref:`standard warning filters `. + +Backward compatibilities in ``Parser.addoption`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 2.4 + +Several behaviors of :meth:`Parser.addoption <_pytest.config.argparsing.Parser.addoption>` are now +scheduled for removal in pytest 7 (deprecated since pytest 2.4.0): + +- ``parser.addoption(..., help=".. %default ..")`` - use ``%(default)s`` instead. +- ``parser.addoption(..., type="int/string/float/complex")`` - use ``type=int`` etc. instead. + + Raising ``unittest.SkipTest`` during collection ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/_pytest/config/argparsing.py b/src/_pytest/config/argparsing.py index 5a09ea781e6..cf738cc2b8e 100644 --- a/src/_pytest/config/argparsing.py +++ b/src/_pytest/config/argparsing.py @@ -18,6 +18,9 @@ import _pytest._io from _pytest.compat import final from _pytest.config.exceptions import UsageError +from _pytest.deprecated import ARGUMENT_PERCENT_DEFAULT +from _pytest.deprecated import ARGUMENT_TYPE_STR +from _pytest.deprecated import ARGUMENT_TYPE_STR_CHOICE if TYPE_CHECKING: from typing import NoReturn @@ -212,12 +215,7 @@ def __init__(self, *names: str, **attrs: Any) -> None: self._short_opts: List[str] = [] self._long_opts: List[str] = [] if "%default" in (attrs.get("help") or ""): - warnings.warn( - 'pytest now uses argparse. "%default" should be' - ' changed to "%(default)s" ', - DeprecationWarning, - stacklevel=3, - ) + warnings.warn(ARGUMENT_PERCENT_DEFAULT, stacklevel=3) try: typ = attrs["type"] except KeyError: @@ -227,11 +225,7 @@ def __init__(self, *names: str, **attrs: Any) -> None: if isinstance(typ, str): if typ == "choice": warnings.warn( - "`type` argument to addoption() is the string %r." - " For choices this is optional and can be omitted, " - " but when supplied should be a type (for example `str` or `int`)." - " (options: %s)" % (typ, names), - DeprecationWarning, + ARGUMENT_TYPE_STR_CHOICE.format(typ=typ, names=names), stacklevel=4, ) # argparse expects a type here take it from @@ -239,11 +233,7 @@ def __init__(self, *names: str, **attrs: Any) -> None: attrs["type"] = type(attrs["choices"][0]) else: warnings.warn( - "`type` argument to addoption() is the string %r, " - " but when supplied should be a type (for example `str` or `int`)." - " (options: %s)" % (typ, names), - DeprecationWarning, - stacklevel=4, + ARGUMENT_TYPE_STR.format(typ=typ, names=names), stacklevel=4 ) attrs["type"] = Argument._typ_map[typ] # Used in test_parseopt -> test_parse_defaultgetter. diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index fa91f909769..5efc004ac94 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -69,6 +69,25 @@ "Use pytest.skip() instead." ) +ARGUMENT_PERCENT_DEFAULT = PytestDeprecationWarning( + 'pytest now uses argparse. "%default" should be changed to "%(default)s"', +) + +ARGUMENT_TYPE_STR_CHOICE = UnformattedWarning( + PytestDeprecationWarning, + "`type` argument to addoption() is the string {typ!r}." + " For choices this is optional and can be omitted, " + " but when supplied should be a type (for example `str` or `int`)." + " (options: {names})", +) + +ARGUMENT_TYPE_STR = UnformattedWarning( + PytestDeprecationWarning, + "`type` argument to addoption() is the string {typ!r}, " + " but when supplied should be a type (for example `str` or `int`)." + " (options: {names})", +) + # You want to make some `__init__` or function "private". # From 16af1a31fd137b7af2d4404af49406d2efa9880d Mon Sep 17 00:00:00 2001 From: pytest bot Date: Fri, 5 Feb 2021 00:24:50 +0000 Subject: [PATCH 0123/2772] [automated] Update plugin list --- doc/en/plugin_list.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/en/plugin_list.rst b/doc/en/plugin_list.rst index 6aae603a8a7..e43f313422a 100644 --- a/doc/en/plugin_list.rst +++ b/doc/en/plugin_list.rst @@ -349,7 +349,7 @@ name `pytest-httpbin `_ Easily test your HTTP library against a local copy of httpbin Feb 11, 2019 5 - Production/Stable N/A `pytest-http-mocker `_ Pytest plugin for http mocking (via https://github.com/vilus/mocker) Oct 20, 2019 N/A N/A `pytest-httpretty `_ A thin wrapper of HTTPretty for pytest Feb 16, 2014 3 - Alpha N/A -`pytest-httpserver `_ pytest-httpserver is a httpserver for pytest Oct 18, 2020 3 - Alpha pytest ; extra == 'dev' +`pytest-httpserver `_ pytest-httpserver is a httpserver for pytest Feb 04, 2021 3 - Alpha pytest ; extra == 'dev' `pytest-httpx `_ Send responses to httpx. Nov 25, 2020 5 - Production/Stable pytest (==6.*) `pytest-hue `_ Visualise PyTest status via your Phillips Hue lights May 09, 2019 N/A N/A `pytest-hypo-25 `_ help hypo module for pytest Jan 12, 2020 3 - Alpha N/A @@ -422,7 +422,7 @@ name `pytest-manual-marker `_ pytest marker for marking manual tests Nov 28, 2018 3 - Alpha pytest `pytest-markdown `_ Test your markdown docs with pytest Jan 15, 2021 4 - Beta pytest (>=6.0.1,<7.0.0) `pytest-marker-bugzilla `_ py.test bugzilla integration plugin, using markers Jan 09, 2020 N/A N/A -`pytest-markers-presence `_ A simple plugin to detect missed pytest tags and markers" Dec 21, 2020 4 - Beta pytest (>=6.0) +`pytest-markers-presence `_ A simple plugin to detect missed pytest tags and markers" Feb 04, 2021 4 - Beta pytest (>=6.0) `pytest-markfiltration `_ UNKNOWN Nov 08, 2011 3 - Alpha N/A `pytest-mark-no-py3 `_ pytest plugin and bowler codemod to help migrate tests to Python 3 May 17, 2019 N/A pytest `pytest-marks `_ UNKNOWN Nov 23, 2012 3 - Alpha N/A @@ -645,13 +645,13 @@ name `pytest-sanic `_ a pytest plugin for Sanic Sep 24, 2020 N/A pytest (>=5.2) `pytest-sanity `_ Dec 07, 2020 N/A N/A `pytest-sa-pg `_ May 14, 2019 N/A N/A -`pytest-sbase `_ A complete web automation framework for end-to-end testing. Jan 31, 2021 5 - Production/Stable N/A +`pytest-sbase `_ A complete web automation framework for end-to-end testing. Feb 04, 2021 5 - Production/Stable N/A `pytest-scenario `_ pytest plugin for test scenarios Feb 06, 2017 3 - Alpha N/A `pytest-schema `_ 👍 Validate return values against a schema-like object in testing Aug 31, 2020 5 - Production/Stable pytest (>=3.5.0) `pytest-securestore `_ An encrypted password store for use within pytest cases Jun 19, 2019 4 - Beta N/A `pytest-select `_ A pytest plugin which allows to (de-)select tests from a file. Jan 18, 2019 3 - Alpha pytest (>=3.0) `pytest-selenium `_ pytest plugin for Selenium Sep 19, 2020 5 - Production/Stable pytest (>=5.0.0) -`pytest-seleniumbase `_ A complete web automation framework for end-to-end testing. Jan 31, 2021 5 - Production/Stable N/A +`pytest-seleniumbase `_ A complete web automation framework for end-to-end testing. Feb 04, 2021 5 - Production/Stable N/A `pytest-selenium-enhancer `_ pytest plugin for Selenium Nov 26, 2020 5 - Production/Stable N/A `pytest-selenium-pdiff `_ A pytest package implementing perceptualdiff for Selenium tests. Apr 06, 2017 2 - Pre-Alpha N/A `pytest-send-email `_ Send pytest execution result email Dec 04, 2019 N/A N/A From e3c0fd3203fb13d9a43dc6cc6e45627e5c33234a Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 4 Feb 2021 23:14:08 -0300 Subject: [PATCH 0124/2772] Update plugin-list every Sunday instead of everyday Every day seems a bit excessive lately, let's make it less frequent. --- .github/workflows/update-plugin-list.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/update-plugin-list.yml b/.github/workflows/update-plugin-list.yml index 3f4ec092bd9..9b071aa3d4f 100644 --- a/.github/workflows/update-plugin-list.yml +++ b/.github/workflows/update-plugin-list.yml @@ -2,8 +2,9 @@ name: Update Plugin List on: schedule: - # Run daily at midnight. - - cron: '0 0 * * *' + # At 00:00 on Sunday. + # https://crontab.guru + - cron: '0 0 * * 0' workflow_dispatch: jobs: From c604f3f0c52b51fc61ba7d208d522c2614aaa10d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 5 Feb 2021 17:47:37 +0100 Subject: [PATCH 0125/2772] doc: Remove confusing fixture sentence There is no previous `test_ehlo` example (it follows much later) - and the same thing is described further down in ""Requesting" fixtures" already. --- doc/en/fixture.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index d72a0e619b5..6ffd77920be 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -174,9 +174,6 @@ Back to fixtures "Fixtures", in the literal sense, are each of the **arrange** steps and data. They're everything that test needs to do its thing. -At a basic level, test functions request fixtures by declaring them as -arguments, as in the ``test_ehlo(smtp_connection):`` in the previous example. - In pytest, "fixtures" are functions you define that serve this purpose. But they don't have to be limited to just the **arrange** steps. They can provide the **act** step, as well, and this can be a powerful technique for designing more From bcfe253f5b5367d8537e011e3a9e56bae220d411 Mon Sep 17 00:00:00 2001 From: Pax <13646646+paxcodes@users.noreply.github.com> Date: Fri, 5 Feb 2021 12:03:58 -0800 Subject: [PATCH 0126/2772] Type annotation for request.param (#8319) --- src/_pytest/fixtures.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 269369642e3..6a57fffd144 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -758,7 +758,7 @@ def __init__( self, request: "FixtureRequest", scope: "_Scope", - param, + param: Any, param_index: int, fixturedef: "FixtureDef[object]", *, From f674f6da9fec826bc8c4e08781e7309322fc12cc Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 31 Jan 2021 01:49:25 +0200 Subject: [PATCH 0127/2772] runner: add a safety assert to SetupState.prepare This ensures that the teardown for the previous item was done properly for this (next) item, i.e. there are no leftover teardowns. --- src/_pytest/hookspec.py | 6 +++--- src/_pytest/runner.py | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index 41c12a2ccd8..b0b8fd53d85 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -509,9 +509,9 @@ def pytest_runtest_teardown(item: "Item", nextitem: Optional["Item"]) -> None: :param nextitem: The scheduled-to-be-next test item (None if no further test item is - scheduled). This argument can be used to perform exact teardowns, - i.e. calling just enough finalizers so that nextitem only needs to - call setup-functions. + scheduled). This argument is used to perform exact teardowns, i.e. + calling just enough finalizers so that nextitem only needs to call + setup functions. """ diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index ae76a247271..124cf531a2d 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -479,15 +479,18 @@ def __init__(self) -> None: def prepare(self, item: Item) -> None: """Setup objects along the collector chain to the item.""" + needed_collectors = item.listchain() + # If a collector fails its setup, fail its entire subtree of items. # The setup is not retried for each item - the same exception is used. for col, (finalizers, prepare_exc) in self.stack.items(): + assert col in needed_collectors, "previous item was not torn down properly" if prepare_exc: raise prepare_exc - needed_collectors = item.listchain() for col in needed_collectors[len(self.stack) :]: assert col not in self.stack + # Push onto the stack. self.stack[col] = ([col.teardown], None) try: col.setup() From f42b68ccaa4a64b3f7ef1cfcff50b4f39b63ceb9 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 31 Jan 2021 12:14:06 +0200 Subject: [PATCH 0128/2772] runner: rename SetupState.prepare -> setup This is the usual terminology we use, and matches better with `teardown_exact()` and `pytest_runtest_setup()`. --- src/_pytest/fixtures.py | 2 +- src/_pytest/runner.py | 22 +++++++++++----------- testing/python/fixtures.py | 6 +++--- testing/test_runner.py | 14 +++++++------- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 269369642e3..40b482d0482 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -372,7 +372,7 @@ def _fill_fixtures_impl(function: "Function") -> None: fi = fm.getfixtureinfo(function.parent, function.obj, None) function._fixtureinfo = fi request = function._request = FixtureRequest(function, _ispytest=True) - fm.session._setupstate.prepare(function) + fm.session._setupstate.setup(function) request._fillfixtures() # Prune out funcargs for jstests. newfuncargs = {} diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 124cf531a2d..153b134fe79 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -151,7 +151,7 @@ def show_test_item(item: Item) -> None: def pytest_runtest_setup(item: Item) -> None: _update_current_test_var(item, "setup") - item.session._setupstate.prepare(item) + item.session._setupstate.setup(item) def pytest_runtest_call(item: Item) -> None: @@ -417,7 +417,7 @@ class SetupState: [] - During the setup phase of item1, prepare(item1) is called. What it does + During the setup phase of item1, setup(item1) is called. What it does is: push session to stack, run session.setup() @@ -441,7 +441,7 @@ class SetupState: [session] - During the setup phase of item2, prepare(item2) is called. What it does + During the setup phase of item2, setup(item2) is called. What it does is: push mod2 to stack, run mod2.setup() @@ -477,16 +477,16 @@ def __init__(self) -> None: ], ] = {} - def prepare(self, item: Item) -> None: + def setup(self, item: Item) -> None: """Setup objects along the collector chain to the item.""" needed_collectors = item.listchain() # If a collector fails its setup, fail its entire subtree of items. # The setup is not retried for each item - the same exception is used. - for col, (finalizers, prepare_exc) in self.stack.items(): + for col, (finalizers, exc) in self.stack.items(): assert col in needed_collectors, "previous item was not torn down properly" - if prepare_exc: - raise prepare_exc + if exc: + raise exc for col in needed_collectors[len(self.stack) :]: assert col not in self.stack @@ -494,9 +494,9 @@ def prepare(self, item: Item) -> None: self.stack[col] = ([col.teardown], None) try: col.setup() - except TEST_OUTCOME as e: - self.stack[col] = (self.stack[col][0], e) - raise e + except TEST_OUTCOME as exc: + self.stack[col] = (self.stack[col][0], exc) + raise exc def addfinalizer(self, finalizer: Callable[[], object], node: Node) -> None: """Attach a finalizer to the given node. @@ -520,7 +520,7 @@ def teardown_exact(self, nextitem: Optional[Item]) -> None: while self.stack: if list(self.stack.keys()) == needed_collectors[: len(self.stack)]: break - node, (finalizers, prepare_exc) = self.stack.popitem() + node, (finalizers, _) = self.stack.popitem() while finalizers: fin = finalizers.pop() try: diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index 3d78ebf5826..3d5099c5399 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -131,7 +131,7 @@ def test_funcarg_basic(self, pytester: Pytester) -> None: item = pytester.getitem(Path("test_funcarg_basic.py")) assert isinstance(item, Function) # Execute's item's setup, which fills fixtures. - item.session._setupstate.prepare(item) + item.session._setupstate.setup(item) del item.funcargs["request"] assert len(get_public_names(item.funcargs)) == 2 assert item.funcargs["some"] == "test_func" @@ -827,7 +827,7 @@ def test_func(something): pass req = item._request # Execute item's setup. - item.session._setupstate.prepare(item) + item.session._setupstate.setup(item) with pytest.raises(pytest.FixtureLookupError): req.getfixturevalue("notexists") @@ -855,7 +855,7 @@ def test_func(something): pass """ ) assert isinstance(item, Function) - item.session._setupstate.prepare(item) + item.session._setupstate.setup(item) item._request._fillfixtures() # successively check finalization calls parent = item.getparent(pytest.Module) diff --git a/testing/test_runner.py b/testing/test_runner.py index e3f2863079f..abb87c6d3d4 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -25,7 +25,7 @@ def test_setup(self, pytester: Pytester) -> None: item = pytester.getitem("def test_func(): pass") ss = item.session._setupstate values = [1] - ss.prepare(item) + ss.setup(item) ss.addfinalizer(values.pop, item) assert values ss.teardown_exact(None) @@ -34,7 +34,7 @@ def test_setup(self, pytester: Pytester) -> None: def test_teardown_exact_stack_empty(self, pytester: Pytester) -> None: item = pytester.getitem("def test_func(): pass") ss = item.session._setupstate - ss.prepare(item) + ss.setup(item) ss.teardown_exact(None) ss.teardown_exact(None) ss.teardown_exact(None) @@ -49,9 +49,9 @@ def test_func(): pass ) ss = item.session._setupstate with pytest.raises(ValueError): - ss.prepare(item) + ss.setup(item) with pytest.raises(ValueError): - ss.prepare(item) + ss.setup(item) def test_teardown_multiple_one_fails(self, pytester: Pytester) -> None: r = [] @@ -67,7 +67,7 @@ def fin3(): item = pytester.getitem("def test_func(): pass") ss = item.session._setupstate - ss.prepare(item) + ss.setup(item) ss.addfinalizer(fin1, item) ss.addfinalizer(fin2, item) ss.addfinalizer(fin3, item) @@ -87,7 +87,7 @@ def fin2(): item = pytester.getitem("def test_func(): pass") ss = item.session._setupstate - ss.prepare(item) + ss.setup(item) ss.addfinalizer(fin1, item) ss.addfinalizer(fin2, item) with pytest.raises(Exception) as err: @@ -106,7 +106,7 @@ def fin_module(): item = pytester.getitem("def test_func(): pass") mod = item.listchain()[-2] ss = item.session._setupstate - ss.prepare(item) + ss.setup(item) ss.addfinalizer(fin_module, mod) ss.addfinalizer(fin_func, item) with pytest.raises(Exception, match="oops1"): From 5822888d735e2cd617225686611275fa8fbafbea Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 31 Jan 2021 12:23:10 +0200 Subject: [PATCH 0129/2772] runner: add clarifying comments on why runtestprotocol re-inits the FixtureRequest --- src/_pytest/runner.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 153b134fe79..e43dd2dc818 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -120,6 +120,8 @@ def runtestprotocol( ) -> List[TestReport]: hasrequest = hasattr(item, "_request") if hasrequest and not item._request: # type: ignore[attr-defined] + # This only happens if the item is re-run, as is done by + # pytest-rerunfailures. item._initrequest() # type: ignore[attr-defined] rep = call_and_report(item, "setup", log) reports = [rep] From 3c6bd7eb27cad576520ddae5d615df6b5ad47dc7 Mon Sep 17 00:00:00 2001 From: pytest bot Date: Sun, 7 Feb 2021 00:44:13 +0000 Subject: [PATCH 0130/2772] [automated] Update plugin list --- doc/en/plugin_list.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/en/plugin_list.rst b/doc/en/plugin_list.rst index e43f313422a..cb70b0dcfdf 100644 --- a/doc/en/plugin_list.rst +++ b/doc/en/plugin_list.rst @@ -3,7 +3,7 @@ Plugins List PyPI projects that match "pytest-\*" are considered plugins and are listed automatically. Packages classified as inactive are excluded. -This list contains 821 plugins. +This list contains 822 plugins. ============================================================================================================== ======================================================================================================================================================================== ============== ===================== ============================================ name summary last release status requires @@ -499,6 +499,7 @@ name `pytest-oot `_ Run object-oriented tests in a simple format Sep 18, 2016 4 - Beta N/A `pytest-openfiles `_ Pytest plugin for detecting inadvertent open file handles Apr 16, 2020 3 - Alpha pytest (>=4.6) `pytest-opentmi `_ pytest plugin for publish results to opentmi Jun 10, 2020 5 - Production/Stable pytest (>=5.0) +`pytest-operator `_ Fixtures for Operators Feb 04, 2021 N/A N/A `pytest-optional `_ include/exclude values of fixtures in pytest Oct 07, 2015 N/A N/A `pytest-optional-tests `_ Easy declaration of optional tests (i.e., that are not run by default) Jul 09, 2019 4 - Beta pytest (>=4.5.0) `pytest-orchestration `_ A pytest plugin for orchestrating tests Jul 18, 2019 N/A N/A @@ -630,7 +631,7 @@ name `pytest-reverse `_ Pytest plugin to reverse test order. Dec 27, 2020 5 - Production/Stable pytest `pytest-ringo `_ pytest plugin to test webapplications using the Ringo webframework Sep 27, 2017 3 - Alpha N/A `pytest-rng `_ Fixtures for seeding tests and making randomness reproducible Aug 08, 2019 5 - Production/Stable pytest -`pytest-roast `_ pytest plugin for ROAST configuration override and fixtures Jan 14, 2021 5 - Production/Stable pytest (<6) +`pytest-roast `_ pytest plugin for ROAST configuration override and fixtures Feb 05, 2021 5 - Production/Stable pytest (<6) `pytest-rotest `_ Pytest integration with rotest Sep 08, 2019 N/A pytest (>=3.5.0) `pytest-rpc `_ Extend py.test for RPC OpenStack testing. Feb 22, 2019 4 - Beta pytest (~=3.6) `pytest-rt `_ pytest data collector plugin for Testgr Jan 24, 2021 N/A N/A @@ -645,13 +646,13 @@ name `pytest-sanic `_ a pytest plugin for Sanic Sep 24, 2020 N/A pytest (>=5.2) `pytest-sanity `_ Dec 07, 2020 N/A N/A `pytest-sa-pg `_ May 14, 2019 N/A N/A -`pytest-sbase `_ A complete web automation framework for end-to-end testing. Feb 04, 2021 5 - Production/Stable N/A +`pytest-sbase `_ A complete web automation framework for end-to-end testing. Feb 06, 2021 5 - Production/Stable N/A `pytest-scenario `_ pytest plugin for test scenarios Feb 06, 2017 3 - Alpha N/A `pytest-schema `_ 👍 Validate return values against a schema-like object in testing Aug 31, 2020 5 - Production/Stable pytest (>=3.5.0) `pytest-securestore `_ An encrypted password store for use within pytest cases Jun 19, 2019 4 - Beta N/A `pytest-select `_ A pytest plugin which allows to (de-)select tests from a file. Jan 18, 2019 3 - Alpha pytest (>=3.0) `pytest-selenium `_ pytest plugin for Selenium Sep 19, 2020 5 - Production/Stable pytest (>=5.0.0) -`pytest-seleniumbase `_ A complete web automation framework for end-to-end testing. Feb 04, 2021 5 - Production/Stable N/A +`pytest-seleniumbase `_ A complete web automation framework for end-to-end testing. Feb 06, 2021 5 - Production/Stable N/A `pytest-selenium-enhancer `_ pytest plugin for Selenium Nov 26, 2020 5 - Production/Stable N/A `pytest-selenium-pdiff `_ A pytest package implementing perceptualdiff for Selenium tests. Apr 06, 2017 2 - Pre-Alpha N/A `pytest-send-email `_ Send pytest execution result email Dec 04, 2019 N/A N/A From e8d7a7b843972399d457ab6a912855bac90e61fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Feb 2021 03:01:58 +0000 Subject: [PATCH 0131/2772] build(deps): bump anyio[curio,trio] in /testing/plugins_integration Bumps [anyio[curio,trio]](https://github.com/agronholm/anyio) from 2.0.2 to 2.1.0. - [Release notes](https://github.com/agronholm/anyio/releases) - [Changelog](https://github.com/agronholm/anyio/blob/master/docs/versionhistory.rst) - [Commits](https://github.com/agronholm/anyio/compare/2.0.2...2.1.0) Signed-off-by: dependabot[bot] --- testing/plugins_integration/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt index 86c2a862c9b..f92f62c6967 100644 --- a/testing/plugins_integration/requirements.txt +++ b/testing/plugins_integration/requirements.txt @@ -1,4 +1,4 @@ -anyio[curio,trio]==2.0.2 +anyio[curio,trio]==2.1.0 django==3.1.5 pytest-asyncio==0.14.0 pytest-bdd==4.0.2 From ef14f286a389c930f54fc4a424fba4e0a5730c1f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Feb 2021 03:02:01 +0000 Subject: [PATCH 0132/2772] build(deps): bump django in /testing/plugins_integration Bumps [django](https://github.com/django/django) from 3.1.5 to 3.1.6. - [Release notes](https://github.com/django/django/releases) - [Commits](https://github.com/django/django/compare/3.1.5...3.1.6) Signed-off-by: dependabot[bot] --- testing/plugins_integration/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt index 86c2a862c9b..d9337f6e034 100644 --- a/testing/plugins_integration/requirements.txt +++ b/testing/plugins_integration/requirements.txt @@ -1,5 +1,5 @@ anyio[curio,trio]==2.0.2 -django==3.1.5 +django==3.1.6 pytest-asyncio==0.14.0 pytest-bdd==4.0.2 pytest-cov==2.11.1 From 6432fc23029c650d7244d9bd103cc7b7ea161a93 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Feb 2021 16:50:48 +0000 Subject: [PATCH 0133/2772] [pre-commit.ci] pre-commit autoupdate --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2eed21fc883..6b2d09e814e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,12 +29,12 @@ repos: - flake8-typing-imports==1.9.0 - flake8-docstrings==1.5.0 - repo: https://github.com/asottile/reorder_python_imports - rev: v2.3.6 + rev: v2.4.0 hooks: - id: reorder-python-imports args: ['--application-directories=.:src', --py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.9.0 + rev: v2.10.0 hooks: - id: pyupgrade args: [--py36-plus] @@ -43,7 +43,7 @@ repos: hooks: - id: setup-cfg-fmt - repo: https://github.com/pre-commit/pygrep-hooks - rev: v1.7.0 + rev: v1.7.1 hooks: - id: python-use-type-annotations - repo: https://github.com/pre-commit/mirrors-mypy From 3d831225bb3e3934e592b9573ab665f4d2922e83 Mon Sep 17 00:00:00 2001 From: Sylvain Bellemare Date: Wed, 10 Feb 2021 22:17:39 +0000 Subject: [PATCH 0134/2772] Remove duplicate '>=' in setup.cfg --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 14fdb6df5c0..ab6b2fb9379 100644 --- a/setup.cfg +++ b/setup.cfg @@ -54,7 +54,7 @@ python_requires = >=3.6 package_dir = =src setup_requires = - setuptools>=>=42.0 + setuptools>=42.0 setuptools-scm>=3.4 zip_safe = no From a0ae5fd652786c18f3f9a6218abbf16a14449b45 Mon Sep 17 00:00:00 2001 From: pytest bot Date: Sun, 14 Feb 2021 00:45:06 +0000 Subject: [PATCH 0135/2772] [automated] Update plugin list --- doc/en/plugin_list.rst | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/doc/en/plugin_list.rst b/doc/en/plugin_list.rst index cb70b0dcfdf..c7c9fdd2957 100644 --- a/doc/en/plugin_list.rst +++ b/doc/en/plugin_list.rst @@ -3,7 +3,7 @@ Plugins List PyPI projects that match "pytest-\*" are considered plugins and are listed automatically. Packages classified as inactive are excluded. -This list contains 822 plugins. +This list contains 827 plugins. ============================================================================================================== ======================================================================================================================================================================== ============== ===================== ============================================ name summary last release status requires @@ -34,7 +34,7 @@ name `pytest-apistellar `_ apistellar plugin for pytest. Jun 18, 2019 N/A N/A `pytest-appengine `_ AppEngine integration that works well with pytest-django Feb 27, 2017 N/A N/A `pytest-appium `_ Pytest plugin for appium Dec 05, 2019 N/A N/A -`pytest-approvaltests `_ A plugin to use approvaltests with pytest Jan 31, 2021 4 - Beta pytest (>=3.5.0) +`pytest-approvaltests `_ A plugin to use approvaltests with pytest Feb 07, 2021 4 - Beta pytest (>=3.5.0) `pytest-arraydiff `_ pytest plugin to help with comparing array output from tests Dec 06, 2018 4 - Beta pytest `pytest-asgi-server `_ Convenient ASGI client/server fixtures for Pytest Dec 12, 2020 N/A pytest (>=5.4.1) `pytest-asptest `_ test Answer Set Programming programs Apr 28, 2018 4 - Beta N/A @@ -148,6 +148,7 @@ name `pytest-csv `_ CSV output for pytest. Jun 24, 2019 N/A pytest (>=4.4) `pytest-curio `_ Pytest support for curio. Oct 07, 2020 N/A N/A `pytest-curl-report `_ pytest plugin to generate curl command line report Dec 11, 2016 4 - Beta N/A +`pytest-custom-concurrency `_ Custom grouping concurrence for pytest Feb 08, 2021 N/A N/A `pytest-custom-exit-code `_ Exit pytest test session with custom exit code in different scenarios Aug 07, 2019 4 - Beta pytest (>=4.0.2) `pytest-custom-report `_ Configure the symbols displayed for test outcomes Jan 30, 2019 N/A pytest `pytest-cython `_ A plugin for testing Cython extension modules Jan 26, 2021 4 - Beta pytest (>=2.7.3) @@ -212,7 +213,7 @@ name `pytest-docker-pexpect `_ pytest plugin for writing functional tests with pexpect and docker Jan 14, 2019 N/A pytest `pytest-docker-postgresql `_ A simple plugin to use with pytest Sep 24, 2019 4 - Beta pytest (>=3.5.0) `pytest-docker-py `_ Easy to use, simple to extend, pytest plugin that minimally leverages docker-py. Nov 27, 2018 N/A pytest (==4.0.0) -`pytest-docker-registry-fixtures `_ Pytest fixtures for testing with docker registries. Feb 03, 2021 4 - Beta pytest +`pytest-docker-registry-fixtures `_ Pytest fixtures for testing with docker registries. Feb 10, 2021 4 - Beta pytest `pytest-docker-tools `_ Docker integration tests for pytest Jan 15, 2021 4 - Beta pytest (>=6.0.1,<7.0.0) `pytest-docs `_ Documentation tool for pytest Nov 11, 2018 4 - Beta pytest (>=3.5.0) `pytest-docstyle `_ pytest plugin to run pydocstyle Mar 23, 2020 3 - Alpha N/A @@ -306,6 +307,7 @@ name `pytest-freezegun `_ Wrap tests with fixtures in freeze_time Jul 19, 2020 4 - Beta pytest (>=3.0.0) `pytest-freeze-reqs `_ Check if requirement files are frozen Nov 14, 2019 N/A N/A `pytest-func-cov `_ Pytest plugin for measuring function coverage May 24, 2020 3 - Alpha pytest (>=5) +`pytest-funparam `_ An alternative way to parametrize test cases Feb 13, 2021 4 - Beta pytest (>=4.6.0) `pytest-fxa `_ pytest plugin for Firefox Accounts Aug 28, 2018 5 - Production/Stable N/A `pytest-fxtest `_ Oct 27, 2020 N/A N/A `pytest-gc `_ The garbage collector plugin for py.test Feb 01, 2018 N/A N/A @@ -313,7 +315,7 @@ name `pytest-gevent `_ Ensure that gevent is properly patched when invoking pytest Feb 25, 2020 N/A pytest `pytest-gherkin `_ A flexible framework for executing BDD gherkin tests Jul 27, 2019 3 - Alpha pytest (>=5.0.0) `pytest-ghostinspector `_ For finding/executing Ghost Inspector tests May 17, 2016 3 - Alpha N/A -`pytest-girder `_ A set of pytest fixtures for testing Girder applications. Jan 18, 2021 N/A N/A +`pytest-girder `_ A set of pytest fixtures for testing Girder applications. Feb 11, 2021 N/A N/A `pytest-git `_ Git repository fixture for py.test May 28, 2019 5 - Production/Stable pytest `pytest-gitcov `_ Pytest plugin for reporting on coverage of the last git commit. Jan 11, 2020 2 - Pre-Alpha N/A `pytest-git-fixtures `_ Pytest fixtures for testing with git. Jan 25, 2021 4 - Beta pytest @@ -337,7 +339,7 @@ name `pytest-historic `_ Custom report to display pytest historical execution records Apr 08, 2020 N/A pytest `pytest-historic-hook `_ Custom listener to store execution results into MYSQL DB, which is used for pytest-historic report Apr 08, 2020 N/A pytest `pytest-homeassistant `_ A pytest plugin for use with homeassistant custom components. Aug 12, 2020 4 - Beta N/A -`pytest-homeassistant-custom-component `_ Experimental package to automatically extract test plugins for Home Assistant custom components Feb 01, 2021 3 - Alpha pytest (==6.2.2) +`pytest-homeassistant-custom-component `_ Experimental package to automatically extract test plugins for Home Assistant custom components Feb 10, 2021 3 - Alpha pytest (==6.2.2) `pytest-honors `_ Report on tests that honor constraints, and guard against regressions Mar 06, 2020 4 - Beta N/A `pytest-hoverfly-wrapper `_ Integrates the Hoverfly HTTP proxy into Pytest Jan 31, 2021 4 - Beta N/A `pytest-html `_ pytest plugin for generating HTML reports Dec 13, 2020 5 - Production/Stable pytest (!=6.0.0,>=5.0) @@ -353,7 +355,7 @@ name `pytest-httpx `_ Send responses to httpx. Nov 25, 2020 5 - Production/Stable pytest (==6.*) `pytest-hue `_ Visualise PyTest status via your Phillips Hue lights May 09, 2019 N/A N/A `pytest-hypo-25 `_ help hypo module for pytest Jan 12, 2020 3 - Alpha N/A -`pytest-ibutsu `_ A plugin to sent pytest results to an Ibutsu server Dec 02, 2020 4 - Beta pytest +`pytest-ibutsu `_ A plugin to sent pytest results to an Ibutsu server Feb 11, 2021 4 - Beta pytest `pytest-icdiff `_ use icdiff for better error messages in pytest assertions Apr 08, 2020 4 - Beta N/A `pytest-idapro `_ A pytest plugin for idapython. Allows a pytest setup to run tests outside and inside IDA in an automated manner by runnig pytest inside IDA and by mocking idapython api Nov 03, 2018 N/A N/A `pytest-ignore-flaky `_ ignore failures from flaky tests (pytest plugin) Jan 14, 2019 5 - Production/Stable pytest (>=3.7) @@ -381,6 +383,7 @@ name `pytest-jasmine `_ Run jasmine tests from your pytest test suite Nov 04, 2017 1 - Planning N/A `pytest-jest `_ A custom jest-pytest oriented Pytest reporter May 22, 2018 4 - Beta pytest (>=3.3.2) `pytest-jira `_ py.test JIRA integration plugin, using markers Nov 29, 2019 N/A N/A +`pytest-jira-xray `_ pytest plugin to integrate tests with JIRA XRAY Feb 12, 2021 3 - Alpha pytest `pytest-jobserver `_ Limit parallel tests with posix jobserver. May 15, 2019 5 - Production/Stable pytest `pytest-joke `_ Test failures are better served with humor. Oct 08, 2019 4 - Beta pytest (>=4.2.1) `pytest-json `_ Generate JSON test reports Jan 18, 2016 4 - Beta N/A @@ -457,7 +460,7 @@ name `pytest-molecule `_ PyTest Molecule Plugin :: discover and run molecule tests Jan 25, 2021 5 - Production/Stable N/A `pytest-mongo `_ MongoDB process and client fixtures plugin for py.test. Jan 12, 2021 5 - Production/Stable pytest (>=3.0.0) `pytest-mongodb `_ pytest plugin for MongoDB fixtures Dec 07, 2019 5 - Production/Stable pytest (>=2.5.2) -`pytest-monitor `_ Pytest plugin for analyzing resource usage. Nov 20, 2020 5 - Production/Stable pytest +`pytest-monitor `_ Pytest plugin for analyzing resource usage. Feb 07, 2021 5 - Production/Stable pytest `pytest-monkeyplus `_ pytest's monkeypatch subclass with extra functionalities Sep 18, 2012 5 - Production/Stable N/A `pytest-monkeytype `_ pytest-monkeytype: Generate Monkeytype annotations from your pytest tests. Jul 29, 2020 4 - Beta N/A `pytest-moto `_ Fixtures for integration tests of AWS services,uses moto mocking library. Aug 28, 2015 1 - Planning N/A @@ -499,7 +502,7 @@ name `pytest-oot `_ Run object-oriented tests in a simple format Sep 18, 2016 4 - Beta N/A `pytest-openfiles `_ Pytest plugin for detecting inadvertent open file handles Apr 16, 2020 3 - Alpha pytest (>=4.6) `pytest-opentmi `_ pytest plugin for publish results to opentmi Jun 10, 2020 5 - Production/Stable pytest (>=5.0) -`pytest-operator `_ Fixtures for Operators Feb 04, 2021 N/A N/A +`pytest-operator `_ Fixtures for Operators Feb 12, 2021 N/A N/A `pytest-optional `_ include/exclude values of fixtures in pytest Oct 07, 2015 N/A N/A `pytest-optional-tests `_ Easy declaration of optional tests (i.e., that are not run by default) Jul 09, 2019 4 - Beta pytest (>=4.5.0) `pytest-orchestration `_ A pytest plugin for orchestrating tests Jul 18, 2019 N/A N/A @@ -515,6 +518,7 @@ name `pytest-parametrized `_ Pytest plugin for parametrizing tests with default iterables. Oct 19, 2020 5 - Production/Stable pytest `pytest-parawtf `_ Finally spell paramete?ri[sz]e correctly Dec 03, 2018 4 - Beta pytest (>=3.6.0) `pytest-pass `_ Check out https://github.com/elilutsky/pytest-pass Dec 04, 2019 N/A N/A +`pytest-passrunner `_ Pytest plugin providing the 'run_on_pass' marker Feb 10, 2021 5 - Production/Stable pytest (>=4.6.0) `pytest-paste-config `_ Allow setting the path to a paste config file Sep 18, 2013 3 - Alpha N/A `pytest-pdb `_ pytest plugin which adds pdb helper commands related to pytest. Jul 31, 2018 N/A N/A `pytest-peach `_ pytest plugin for fuzzing with Peach API Security Apr 12, 2019 4 - Beta pytest (>=2.8.7) @@ -549,7 +553,7 @@ name `pytest-pop `_ A pytest plugin to help with testing pop projects Aug 13, 2020 5 - Production/Stable pytest (>=5.4.0) `pytest-portion `_ Select a portion of the collected tests Jan 28, 2021 4 - Beta pytest (>=3.5.0) `pytest-postgres `_ Run PostgreSQL in Docker container in Pytest. Mar 22, 2020 N/A pytest -`pytest-postgresql `_ Postgresql fixtures and fixture factories for Pytest. Jan 31, 2021 5 - Production/Stable pytest (>=3.0.0) +`pytest-postgresql `_ Postgresql fixtures and fixture factories for Pytest. Feb 11, 2021 5 - Production/Stable pytest (>=3.0.0) `pytest-power `_ pytest plugin with powerful fixtures Dec 31, 2020 N/A pytest (>=5.4) `pytest-pride `_ Minitest-style test colors Apr 02, 2016 3 - Alpha N/A `pytest-print `_ pytest-print adds the printer fixture you can use to print messages to the user (directly to the pytest runner, not stdout) Oct 23, 2020 5 - Production/Stable pytest (>=3.0.0) @@ -637,7 +641,7 @@ name `pytest-rt `_ pytest data collector plugin for Testgr Jan 24, 2021 N/A N/A `pytest-rts `_ Coverage-based regression test selection (RTS) plugin for pytest Jan 29, 2021 N/A pytest `pytest-runfailed `_ implement a --failed option for pytest Mar 24, 2016 N/A N/A -`pytest-runner `_ Invoke py.test as distutils command with dependency resolution Oct 26, 2019 5 - Production/Stable pytest (!=3.7.3,>=3.5) ; extra == 'testing' +`pytest-runner `_ Invoke py.test as distutils command with dependency resolution Feb 12, 2021 5 - Production/Stable pytest (!=3.7.3,>=3.5) ; extra == 'testing' `pytest-salt `_ Pytest Salt Plugin Jan 27, 2020 4 - Beta N/A `pytest-salt-containers `_ A Pytest plugin that builds and creates docker containers Nov 09, 2016 4 - Beta N/A `pytest-salt-factories `_ Pytest Salt Plugin Jan 19, 2021 4 - Beta pytest (>=6.1.1) @@ -646,19 +650,19 @@ name `pytest-sanic `_ a pytest plugin for Sanic Sep 24, 2020 N/A pytest (>=5.2) `pytest-sanity `_ Dec 07, 2020 N/A N/A `pytest-sa-pg `_ May 14, 2019 N/A N/A -`pytest-sbase `_ A complete web automation framework for end-to-end testing. Feb 06, 2021 5 - Production/Stable N/A +`pytest-sbase `_ A complete web automation framework for end-to-end testing. Feb 13, 2021 5 - Production/Stable N/A `pytest-scenario `_ pytest plugin for test scenarios Feb 06, 2017 3 - Alpha N/A `pytest-schema `_ 👍 Validate return values against a schema-like object in testing Aug 31, 2020 5 - Production/Stable pytest (>=3.5.0) `pytest-securestore `_ An encrypted password store for use within pytest cases Jun 19, 2019 4 - Beta N/A `pytest-select `_ A pytest plugin which allows to (de-)select tests from a file. Jan 18, 2019 3 - Alpha pytest (>=3.0) `pytest-selenium `_ pytest plugin for Selenium Sep 19, 2020 5 - Production/Stable pytest (>=5.0.0) -`pytest-seleniumbase `_ A complete web automation framework for end-to-end testing. Feb 06, 2021 5 - Production/Stable N/A +`pytest-seleniumbase `_ A complete web automation framework for end-to-end testing. Feb 13, 2021 5 - Production/Stable N/A `pytest-selenium-enhancer `_ pytest plugin for Selenium Nov 26, 2020 5 - Production/Stable N/A `pytest-selenium-pdiff `_ A pytest package implementing perceptualdiff for Selenium tests. Apr 06, 2017 2 - Pre-Alpha N/A `pytest-send-email `_ Send pytest execution result email Dec 04, 2019 N/A N/A `pytest-sentry `_ A pytest plugin to send testrun information to Sentry.io Dec 16, 2020 N/A N/A `pytest-server-fixtures `_ Extensible server fixures for py.test May 28, 2019 5 - Production/Stable pytest -`pytest-serverless `_ Automatically mocks resources from serverless.yml in pytest using moto. Dec 26, 2020 4 - Beta N/A +`pytest-serverless `_ Automatically mocks resources from serverless.yml in pytest using moto. Feb 11, 2021 4 - Beta N/A `pytest-services `_ Services plugin for pytest testing framework Oct 30, 2020 6 - Mature N/A `pytest-session2file `_ pytest-session2file (aka: pytest-session_to_file for v0.1.0 - v0.1.2) is a py.test plugin for capturing and saving to file the stdout of py.test. Jan 26, 2021 3 - Alpha pytest `pytest-session-fixture-globalize `_ py.test plugin to make session fixtures behave as if written in conftest, even if it is written in some modules May 15, 2018 4 - Beta N/A @@ -691,12 +695,12 @@ name `pytest-spawner `_ py.test plugin to spawn process and communicate with them. Jul 31, 2015 4 - Beta N/A `pytest-spec `_ Library pytest-spec is a pytest plugin to display test execution output like a SPECIFICATION. Jan 14, 2021 N/A N/A `pytest-sphinx `_ Doctest plugin for pytest with support for Sphinx-specific doctest-directives Aug 05, 2020 4 - Beta N/A -`pytest-spiratest `_ Exports unit tests as test runs in SpiraTest/Team/Plan Oct 30, 2020 N/A N/A +`pytest-spiratest `_ Exports unit tests as test runs in SpiraTest/Team/Plan Feb 12, 2021 N/A N/A `pytest-splinter `_ Splinter plugin for pytest testing framework Dec 25, 2020 6 - Mature N/A `pytest-split `_ Pytest plugin for splitting test suite based on test execution time Apr 07, 2020 1 - Planning N/A `pytest-splitio `_ Split.io SDK integration for e2e tests Sep 22, 2020 N/A pytest (<7,>=5.0) `pytest-split-tests `_ A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. Forked from Mark Adams' original project pytest-test-groups. May 28, 2019 N/A pytest (>=2.5) -`pytest-splunk-addon `_ A Dynamic test tool for Splunk Apps and Add-ons Feb 01, 2021 N/A pytest (>5.4.0,<6.1) +`pytest-splunk-addon `_ A Dynamic test tool for Splunk Apps and Add-ons Feb 10, 2021 N/A pytest (>5.4.0,<6.1) `pytest-splunk-addon-ui-smartx `_ Library to support testing Splunk Add-on UX Jan 18, 2021 N/A N/A `pytest-splunk-env `_ pytest fixtures for interaction with Splunk Enterprise and Splunk Cloud Oct 22, 2020 N/A pytest (>=6.1.1,<7.0.0) `pytest-sqitch `_ sqitch for pytest Apr 06, 2020 4 - Beta N/A @@ -708,7 +712,7 @@ name `pytest-stepfunctions `_ A small description Jul 07, 2020 4 - Beta pytest `pytest-steps `_ Create step-wise / incremental tests in pytest. Apr 25, 2020 5 - Production/Stable N/A `pytest-stepwise `_ Run a test suite one failing test at a time. Dec 01, 2015 4 - Beta N/A -`pytest-stoq `_ A plugin to pytest stoq Nov 04, 2020 4 - Beta N/A +`pytest-stoq `_ A plugin to pytest stoq Feb 09, 2021 4 - Beta N/A `pytest-stress `_ A Pytest plugin that allows you to loop tests for a user defined amount of time. Dec 07, 2019 4 - Beta pytest (>=3.6.0) `pytest-structlog `_ Structured logging assertions Jul 16, 2020 N/A pytest `pytest-structmpd `_ provide structured temporary directory Oct 17, 2018 N/A N/A @@ -795,6 +799,7 @@ name `pytest-vcrpandas `_ Test from HTTP interactions to dataframe processed. Jan 12, 2019 4 - Beta pytest `pytest-venv `_ py.test fixture for creating a virtual environment Aug 04, 2020 4 - Beta pytest `pytest-verbose-parametrize `_ More descriptive output for parametrized py.test tests May 28, 2019 5 - Production/Stable pytest +`pytest-vimqf `_ A simple pytest plugin that will shrink pytest output when specified, to fit vim quickfix window. Feb 08, 2021 4 - Beta pytest (>=6.2.2,<7.0.0) `pytest-virtualenv `_ Virtualenv fixture for py.test May 28, 2019 5 - Production/Stable pytest `pytest-voluptuous `_ Pytest plugin for asserting data against voluptuous schema. Jun 09, 2020 N/A pytest `pytest-vscodedebug `_ A pytest plugin to easily enable debugging tests within Visual Studio Code Dec 04, 2020 4 - Beta N/A @@ -810,7 +815,7 @@ name `pytest-wholenodeid `_ pytest addon for displaying the whole node id for failures Aug 26, 2015 4 - Beta pytest (>=2.0) `pytest-winnotify `_ Windows tray notifications for py.test results. Apr 22, 2016 N/A N/A `pytest-workflow `_ A pytest plugin for configuring workflow/pipeline tests using YAML files Dec 14, 2020 5 - Production/Stable pytest (>=5.4.0) -`pytest-xdist `_ pytest xdist plugin for distributed testing and loop-on-failing modes Dec 14, 2020 5 - Production/Stable pytest (>=6.0.0) +`pytest-xdist `_ pytest xdist plugin for distributed testing and loop-on-failing modes Feb 09, 2021 5 - Production/Stable pytest (>=6.0.0) `pytest-xdist-debug-for-graingert `_ pytest xdist plugin for distributed testing and loop-on-failing modes Jul 24, 2019 5 - Production/Stable pytest (>=4.4.0) `pytest-xdist-forked `_ forked from pytest-xdist Feb 10, 2020 5 - Production/Stable pytest (>=4.4.0) `pytest-xfiles `_ Pytest fixtures providing data read from function, module or package related (x)files. Feb 27, 2018 N/A N/A From 532543b4ef0d35eeac9e8c04a937ab7190ae5988 Mon Sep 17 00:00:00 2001 From: maskypy40 <60002142+maskypy40@users.noreply.github.com> Date: Wed, 17 Feb 2021 09:33:04 +0100 Subject: [PATCH 0136/2772] Remove empty lines from code-block In assert.rst at line 175 and further there is an example of an assert encountering comparisons. The code-block for this example starts with a comment (line 177) and then it has 2 empty lines. Comparing this example code (test_assert2.py) with the previously mentioned example code on the same page (i.e. test_assert1.py) you can see that there should not be 2 empty lines after the comment. These 2 empty lines are removed. --- doc/en/assert.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/doc/en/assert.rst b/doc/en/assert.rst index b83e30e76db..e6a23bcf3ac 100644 --- a/doc/en/assert.rst +++ b/doc/en/assert.rst @@ -175,8 +175,6 @@ when it encounters comparisons. For example: .. code-block:: python # content of test_assert2.py - - def test_set_comparison(): set1 = set("1308") set2 = set("8035") From 565fe0fb7d19a5cb66fe0d11a273d38fad856d28 Mon Sep 17 00:00:00 2001 From: Vincent Poulailleau Date: Thu, 18 Feb 2021 15:27:39 +0100 Subject: [PATCH 0137/2772] Update number of plugins According to the source, there are 801 plugins now! --- doc/en/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/index.rst b/doc/en/index.rst index 0361805d95a..084725ec2c1 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -66,7 +66,7 @@ Features - Python 3.6+ and PyPy 3 -- Rich plugin architecture, with over 315+ :doc:`external plugins ` and thriving community +- Rich plugin architecture, with over 800+ :doc:`external plugins ` and thriving community Documentation From e6012612b9a7fe2ef6a6c5f46b95855d5977d438 Mon Sep 17 00:00:00 2001 From: Feanil Patel Date: Fri, 19 Feb 2021 13:46:29 -0500 Subject: [PATCH 0138/2772] Remove a redundant paragraph doc This paragraph looks like it is a more verbose version of the sentence right above it. Removing it doesn't reduce the amount of information here but does make the section flow a little better. --- doc/en/fixture.rst | 6 ------ 1 file changed, 6 deletions(-) diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index 6ffd77920be..028786f6523 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -228,12 +228,6 @@ At a basic level, test functions request fixtures by declaring them as arguments, as in the ``test_my_fruit_in_basket(my_fruit, fruit_basket):`` in the previous example. -At a basic level, pytest depends on a test to tell it what fixtures it needs, so -we have to build that information into the test itself. We have to make the test -"**request**" the fixtures it depends on, and to do this, we have to -list those fixtures as parameters in the test function's "signature" (which is -the ``def test_something(blah, stuff, more):`` line). - When pytest goes to run a test, it looks at the parameters in that test function's signature, and then searches for fixtures that have the same names as those parameters. Once pytest finds them, it runs those fixtures, captures what From 0e5e4e03e64c9a607bcaf40f8192ca3049f8db53 Mon Sep 17 00:00:00 2001 From: Matthew Hughes Date: Sat, 20 Feb 2021 18:01:42 +0000 Subject: [PATCH 0139/2772] Remove some unused 'tmpdir's --- testing/code/test_source.py | 4 +--- testing/python/collect.py | 2 +- testing/python/integration.py | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/testing/code/test_source.py b/testing/code/test_source.py index 083a7911f55..5f2c6b1ea54 100644 --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -286,9 +286,7 @@ def g(): assert lines == ["def f():", " def g():", " pass"] -def test_source_of_class_at_eof_without_newline( - tmpdir, _sys_snapshot, tmp_path: Path -) -> None: +def test_source_of_class_at_eof_without_newline(_sys_snapshot, tmp_path: Path) -> None: # this test fails because the implicit inspect.getsource(A) below # does not return the "x = 1" last line. source = Source( diff --git a/testing/python/collect.py b/testing/python/collect.py index c52fb017d0c..22ac6464b89 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -274,7 +274,7 @@ def test_function_as_object_instance_ignored(self, pytester: Pytester) -> None: pytester.makepyfile( """ class A(object): - def __call__(self, tmpdir): + def __call__(self): 0/0 test_a = A() diff --git a/testing/python/integration.py b/testing/python/integration.py index 8576fcee341..77ebfa23ef2 100644 --- a/testing/python/integration.py +++ b/testing/python/integration.py @@ -234,7 +234,7 @@ def mock_basename(path): @mock.patch("os.path.abspath") @mock.patch("os.path.normpath") @mock.patch("os.path.basename", new=mock_basename) - def test_someting(normpath, abspath, tmpdir): + def test_someting(normpath, abspath): abspath.return_value = "this" os.path.normpath(os.path.abspath("hello")) normpath.assert_any_call("this") From 09d4c5e30a54ea1a914d75d19e0762c53a264566 Mon Sep 17 00:00:00 2001 From: Matthew Hughes Date: Sat, 20 Feb 2021 18:05:43 +0000 Subject: [PATCH 0140/2772] Rename variables 'tmpdir'->'tmp_path' Rename this variables reflecting the migrations made with 3bcd316f0 and ed658d682 --- testing/test_collection.py | 50 ++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/testing/test_collection.py b/testing/test_collection.py index 3dd9283eced..cf34ef118ca 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -127,16 +127,16 @@ def test_foo(): class TestCollectFS: def test_ignored_certain_directories(self, pytester: Pytester) -> None: - tmpdir = pytester.path - ensure_file(tmpdir / "build" / "test_notfound.py") - ensure_file(tmpdir / "dist" / "test_notfound.py") - ensure_file(tmpdir / "_darcs" / "test_notfound.py") - ensure_file(tmpdir / "CVS" / "test_notfound.py") - ensure_file(tmpdir / "{arch}" / "test_notfound.py") - ensure_file(tmpdir / ".whatever" / "test_notfound.py") - ensure_file(tmpdir / ".bzr" / "test_notfound.py") - ensure_file(tmpdir / "normal" / "test_found.py") - for x in Path(str(tmpdir)).rglob("test_*.py"): + tmp_path = pytester.path + ensure_file(tmp_path / "build" / "test_notfound.py") + ensure_file(tmp_path / "dist" / "test_notfound.py") + ensure_file(tmp_path / "_darcs" / "test_notfound.py") + ensure_file(tmp_path / "CVS" / "test_notfound.py") + ensure_file(tmp_path / "{arch}" / "test_notfound.py") + ensure_file(tmp_path / ".whatever" / "test_notfound.py") + ensure_file(tmp_path / ".bzr" / "test_notfound.py") + ensure_file(tmp_path / "normal" / "test_found.py") + for x in Path(str(tmp_path)).rglob("test_*.py"): x.write_text("def test_hello(): pass", "utf-8") result = pytester.runpytest("--collect-only") @@ -226,10 +226,12 @@ def test_custom_norecursedirs(self, pytester: Pytester) -> None: norecursedirs = mydir xyz* """ ) - tmpdir = pytester.path - ensure_file(tmpdir / "mydir" / "test_hello.py").write_text("def test_1(): pass") - ensure_file(tmpdir / "xyz123" / "test_2.py").write_text("def test_2(): 0/0") - ensure_file(tmpdir / "xy" / "test_ok.py").write_text("def test_3(): pass") + tmp_path = pytester.path + ensure_file(tmp_path / "mydir" / "test_hello.py").write_text( + "def test_1(): pass" + ) + ensure_file(tmp_path / "xyz123" / "test_2.py").write_text("def test_2(): 0/0") + ensure_file(tmp_path / "xy" / "test_ok.py").write_text("def test_3(): pass") rec = pytester.inline_run() rec.assertoutcome(passed=1) rec = pytester.inline_run("xyz123/test_2.py") @@ -242,10 +244,10 @@ def test_testpaths_ini(self, pytester: Pytester, monkeypatch: MonkeyPatch) -> No testpaths = gui uts """ ) - tmpdir = pytester.path - ensure_file(tmpdir / "env" / "test_1.py").write_text("def test_env(): pass") - ensure_file(tmpdir / "gui" / "test_2.py").write_text("def test_gui(): pass") - ensure_file(tmpdir / "uts" / "test_3.py").write_text("def test_uts(): pass") + tmp_path = pytester.path + ensure_file(tmp_path / "env" / "test_1.py").write_text("def test_env(): pass") + ensure_file(tmp_path / "gui" / "test_2.py").write_text("def test_gui(): pass") + ensure_file(tmp_path / "uts" / "test_3.py").write_text("def test_uts(): pass") # executing from rootdir only tests from `testpaths` directories # are collected @@ -255,7 +257,7 @@ def test_testpaths_ini(self, pytester: Pytester, monkeypatch: MonkeyPatch) -> No # check that explicitly passing directories in the command-line # collects the tests for dirname in ("env", "gui", "uts"): - items, reprec = pytester.inline_genitems(tmpdir.joinpath(dirname)) + items, reprec = pytester.inline_genitems(tmp_path.joinpath(dirname)) assert [x.name for x in items] == ["test_%s" % dirname] # changing cwd to each subdirectory and running pytest without @@ -628,9 +630,9 @@ def test_method(self): class Test_getinitialnodes: def test_global_file(self, pytester: Pytester) -> None: - tmpdir = pytester.path - x = ensure_file(tmpdir / "x.py") - with tmpdir.cwd(): + tmp_path = pytester.path + x = ensure_file(tmp_path / "x.py") + with tmp_path.cwd(): config = pytester.parseconfigure(x) col = pytester.getnode(config, x) assert isinstance(col, pytest.Module) @@ -645,8 +647,8 @@ def test_pkgfile(self, pytester: Pytester) -> None: The parent chain should match: Module -> Package -> Session. Session's parent should always be None. """ - tmpdir = pytester.path - subdir = tmpdir.joinpath("subdir") + tmp_path = pytester.path + subdir = tmp_path.joinpath("subdir") x = ensure_file(subdir / "x.py") ensure_file(subdir / "__init__.py") with subdir.cwd(): From 56421aed0127d034841fc3af8b5039c1b63fe983 Mon Sep 17 00:00:00 2001 From: pytest bot Date: Sun, 21 Feb 2021 00:46:01 +0000 Subject: [PATCH 0141/2772] [automated] Update plugin list --- doc/en/plugin_list.rst | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/doc/en/plugin_list.rst b/doc/en/plugin_list.rst index c7c9fdd2957..5d93204bcd4 100644 --- a/doc/en/plugin_list.rst +++ b/doc/en/plugin_list.rst @@ -3,7 +3,7 @@ Plugins List PyPI projects that match "pytest-\*" are considered plugins and are listed automatically. Packages classified as inactive are excluded. -This list contains 827 plugins. +This list contains 831 plugins. ============================================================================================================== ======================================================================================================================================================================== ============== ===================== ============================================ name summary last release status requires @@ -29,7 +29,7 @@ name `pytest-ansible-playbook `_ Pytest fixture which runs given ansible playbook file. Mar 08, 2019 4 - Beta N/A `pytest-ansible-playbook-runner `_ Pytest fixture which runs given ansible playbook file. Dec 02, 2020 4 - Beta pytest (>=3.1.0) `pytest-antilru `_ Bust functools.lru_cache when running pytest to avoid test pollution Apr 11, 2019 5 - Production/Stable pytest -`pytest-anything `_ Pytest fixtures to assert anything and something Apr 03, 2020 N/A N/A +`pytest-anything `_ Pytest fixtures to assert anything and something Feb 18, 2021 N/A N/A `pytest-aoc `_ Downloads puzzle inputs for Advent of Code and synthesizes PyTest fixtures Dec 01, 2020 N/A pytest ; extra == 'dev' `pytest-apistellar `_ apistellar plugin for pytest. Jun 18, 2019 N/A N/A `pytest-appengine `_ AppEngine integration that works well with pytest-django Feb 27, 2017 N/A N/A @@ -73,6 +73,7 @@ name `pytest-black `_ A pytest plugin to enable format checking with black Oct 05, 2020 4 - Beta N/A `pytest-black-multipy `_ Allow '--black' on older Pythons Jan 14, 2021 5 - Production/Stable pytest (!=3.7.3,>=3.5) ; extra == 'testing' `pytest-blame `_ A pytest plugin helps developers to debug by providing useful commits history. May 04, 2019 N/A pytest (>=4.4.0) +`pytest-blender `_ Blender Pytest plugin. Feb 15, 2021 N/A pytest (==6.2.1) ; extra == 'dev' `pytest-blink1 `_ Pytest plugin to emit notifications via the Blink(1) RGB LED Jan 07, 2018 4 - Beta N/A `pytest-blockage `_ Disable network requests during a test run. Feb 13, 2019 N/A pytest `pytest-blocker `_ pytest plugin to mark a test as blocker and skip all other tests Sep 07, 2015 4 - Beta N/A @@ -95,7 +96,7 @@ name `pytest-canonical-data `_ A plugin which allows to compare results with canonical results, based on previous runs May 08, 2020 2 - Pre-Alpha pytest (>=3.5.0) `pytest-caprng `_ A plugin that replays pRNG state on failure. May 02, 2018 4 - Beta N/A `pytest-capture-deprecatedwarnings `_ pytest plugin to capture all deprecatedwarnings and put them in one file Apr 30, 2019 N/A N/A -`pytest-cases `_ Separate test code from test cases in pytest. Jan 25, 2021 5 - Production/Stable N/A +`pytest-cases `_ Separate test code from test cases in pytest. Feb 19, 2021 5 - Production/Stable N/A `pytest-cassandra `_ Cassandra CCM Test Fixtures for pytest Nov 04, 2017 1 - Planning N/A `pytest-catchlog `_ py.test plugin to catch log messages. This is a fork of pytest-capturelog. Jan 24, 2016 4 - Beta pytest (>=2.6) `pytest-catch-server `_ Pytest plugin with server for catching HTTP requests. Dec 12, 2019 5 - Production/Stable N/A @@ -157,7 +158,7 @@ name `pytest-data `_ Useful functions for managing data for pytest fixtures Nov 01, 2016 5 - Production/Stable N/A `pytest-databricks `_ Pytest plugin for remote Databricks notebooks testing Jul 29, 2020 N/A pytest `pytest-datadir `_ pytest plugin for test data directories and files Oct 22, 2019 5 - Production/Stable pytest (>=2.7.0) -`pytest-datadir-mgr `_ Manager for test data providing downloads, caching of generated files, and a context for temp directories. Jan 08, 2021 5 - Production/Stable pytest (>=6.0.1,<7.0.0) +`pytest-datadir-mgr `_ Manager for test data providing downloads, caching of generated files, and a context for temp directories. Feb 17, 2021 5 - Production/Stable pytest (>=6.0.1,<7.0.0) `pytest-datadir-ng `_ Fixtures for pytest allowing test functions/methods to easily retrieve test resources from the local filesystem. Dec 25, 2019 5 - Production/Stable pytest `pytest-data-file `_ Fixture "data" and "case_data" for test from yaml file Dec 04, 2019 N/A N/A `pytest-datafiles `_ py.test plugin to create a 'tmpdir' containing predefined files/directories. Oct 07, 2018 5 - Production/Stable pytest (>=3.6) @@ -183,7 +184,7 @@ name `pytest-diffeo `_ Common py.test support for Diffeo packages Apr 08, 2016 3 - Alpha N/A `pytest-disable `_ pytest plugin to disable a test and skip it from testrun Sep 10, 2015 4 - Beta N/A `pytest-disable-plugin `_ Disable plugins per test Feb 28, 2019 4 - Beta pytest (>=3.5.0) -`pytest-discord `_ A pytest plugin to notify test results to a Discord channel. Aug 15, 2020 3 - Alpha pytest (!=6.0.0,<7,>=3.3.2) +`pytest-discord `_ A pytest plugin to notify test results to a Discord channel. Feb 14, 2021 3 - Alpha pytest (!=6.0.0,<7,>=3.3.2) `pytest-django `_ A Django plugin for pytest. Oct 22, 2020 5 - Production/Stable pytest (>=5.4.0) `pytest-django-ahead `_ A Django plugin for pytest. Oct 27, 2016 5 - Production/Stable pytest (>=2.9) `pytest-djangoapp `_ Nice pytest plugin to help you with Django pluggable application testing. Sep 21, 2020 4 - Beta N/A @@ -213,7 +214,7 @@ name `pytest-docker-pexpect `_ pytest plugin for writing functional tests with pexpect and docker Jan 14, 2019 N/A pytest `pytest-docker-postgresql `_ A simple plugin to use with pytest Sep 24, 2019 4 - Beta pytest (>=3.5.0) `pytest-docker-py `_ Easy to use, simple to extend, pytest plugin that minimally leverages docker-py. Nov 27, 2018 N/A pytest (==4.0.0) -`pytest-docker-registry-fixtures `_ Pytest fixtures for testing with docker registries. Feb 10, 2021 4 - Beta pytest +`pytest-docker-registry-fixtures `_ Pytest fixtures for testing with docker registries. Feb 17, 2021 4 - Beta pytest `pytest-docker-tools `_ Docker integration tests for pytest Jan 15, 2021 4 - Beta pytest (>=6.0.1,<7.0.0) `pytest-docs `_ Documentation tool for pytest Nov 11, 2018 4 - Beta pytest (>=3.5.0) `pytest-docstyle `_ pytest plugin to run pydocstyle Mar 23, 2020 3 - Alpha N/A @@ -351,7 +352,7 @@ name `pytest-httpbin `_ Easily test your HTTP library against a local copy of httpbin Feb 11, 2019 5 - Production/Stable N/A `pytest-http-mocker `_ Pytest plugin for http mocking (via https://github.com/vilus/mocker) Oct 20, 2019 N/A N/A `pytest-httpretty `_ A thin wrapper of HTTPretty for pytest Feb 16, 2014 3 - Alpha N/A -`pytest-httpserver `_ pytest-httpserver is a httpserver for pytest Feb 04, 2021 3 - Alpha pytest ; extra == 'dev' +`pytest-httpserver `_ pytest-httpserver is a httpserver for pytest Feb 14, 2021 3 - Alpha pytest ; extra == 'dev' `pytest-httpx `_ Send responses to httpx. Nov 25, 2020 5 - Production/Stable pytest (==6.*) `pytest-hue `_ Visualise PyTest status via your Phillips Hue lights May 09, 2019 N/A N/A `pytest-hypo-25 `_ help hypo module for pytest Jan 12, 2020 3 - Alpha N/A @@ -366,7 +367,7 @@ name `pytest-informative-node `_ display more node ininformation. Apr 25, 2019 4 - Beta N/A `pytest-infrastructure `_ pytest stack validation prior to testing executing Apr 12, 2020 4 - Beta N/A `pytest-inmanta `_ A py.test plugin providing fixtures to simplify inmanta modules testing. Oct 12, 2020 5 - Production/Stable N/A -`pytest-inmanta-extensions `_ Inmanta tests package Nov 25, 2020 5 - Production/Stable N/A +`pytest-inmanta-extensions `_ Inmanta tests package Jan 07, 2021 5 - Production/Stable N/A `pytest-Inomaly `_ A simple image diff plugin for pytest Feb 13, 2018 4 - Beta N/A `pytest-insta `_ A practical snapshot testing plugin for pytest Nov 29, 2020 N/A pytest (>=6.0.2,<7.0.0) `pytest-instafail `_ pytest plugin to show failures instantly Jun 14, 2020 4 - Beta pytest (>=2.9) @@ -452,7 +453,7 @@ name `pytest-mock-helper `_ Help you mock HTTP call and generate mock code Jan 24, 2018 N/A pytest `pytest-mockito `_ Base fixtures for mockito Jul 11, 2018 4 - Beta N/A `pytest-mockredis `_ An in-memory mock of a Redis server that runs in a separate thread. This is to be used for unit-tests that require a Redis database. Jan 02, 2018 2 - Pre-Alpha N/A -`pytest-mock-resources `_ A pytest plugin for easily instantiating reproducible mock resources. Oct 08, 2020 N/A pytest (>=1.0) +`pytest-mock-resources `_ A pytest plugin for easily instantiating reproducible mock resources. Feb 17, 2021 N/A pytest (>=1.0) `pytest-mock-server `_ Mock server plugin for pytest Apr 06, 2020 4 - Beta N/A `pytest-mockservers `_ A set of fixtures to test your requests to HTTP/UDP servers Mar 31, 2020 N/A pytest (>=4.3.0) `pytest-modifyjunit `_ Utility for adding additional properties to junit xml for IDM QE Jan 10, 2019 N/A N/A @@ -474,6 +475,7 @@ name `pytest-mypy `_ Mypy static type checker plugin for Pytest Nov 14, 2020 4 - Beta pytest (>=3.5) `pytest-mypyd `_ Mypy static type checker plugin for Pytest Aug 20, 2019 4 - Beta pytest (<4.7,>=2.8) ; python_version < "3.5" `pytest-mypy-plugins `_ pytest plugin for writing tests for mypy plugins Oct 26, 2020 3 - Alpha pytest (>=6.0.0) +`pytest-mypy-plugins-shim `_ Substitute for "pytest-mypy-plugins" for Python implementations which aren't supported by mypy. Feb 14, 2021 N/A pytest (>=6.0.0) `pytest-mypy-testing `_ Pytest plugin to check mypy output. Apr 24, 2020 N/A pytest `pytest-mysql `_ MySQL process and client fixtures for pytest Jul 21, 2020 5 - Production/Stable pytest (>=3.0.0) `pytest-needle `_ pytest plugin for visual testing websites using selenium Dec 10, 2018 4 - Beta pytest (<5.0.0,>=3.0.0) @@ -502,11 +504,11 @@ name `pytest-oot `_ Run object-oriented tests in a simple format Sep 18, 2016 4 - Beta N/A `pytest-openfiles `_ Pytest plugin for detecting inadvertent open file handles Apr 16, 2020 3 - Alpha pytest (>=4.6) `pytest-opentmi `_ pytest plugin for publish results to opentmi Jun 10, 2020 5 - Production/Stable pytest (>=5.0) -`pytest-operator `_ Fixtures for Operators Feb 12, 2021 N/A N/A +`pytest-operator `_ Fixtures for Operators Feb 20, 2021 N/A N/A `pytest-optional `_ include/exclude values of fixtures in pytest Oct 07, 2015 N/A N/A `pytest-optional-tests `_ Easy declaration of optional tests (i.e., that are not run by default) Jul 09, 2019 4 - Beta pytest (>=4.5.0) `pytest-orchestration `_ A pytest plugin for orchestrating tests Jul 18, 2019 N/A N/A -`pytest-order `_ pytest plugin to run your tests in a specific order Jan 27, 2021 4 - Beta pytest (>=3.7) +`pytest-order `_ pytest plugin to run your tests in a specific order Feb 16, 2021 4 - Beta pytest (>=3.7) `pytest-ordering `_ pytest plugin to run your tests in a specific order Nov 14, 2018 4 - Beta pytest `pytest-osxnotify `_ OS X notifications for py.test results. May 15, 2015 N/A N/A `pytest-pact `_ A simple plugin to use with pytest Jan 07, 2019 4 - Beta N/A @@ -571,7 +573,7 @@ name `pytest-pylint `_ pytest plugin to check source code with pylint Nov 09, 2020 5 - Production/Stable pytest (>=5.4) `pytest-pypi `_ Easily test your HTTP library against a local copy of pypi Mar 04, 2018 3 - Alpha N/A `pytest-pypom-navigation `_ Core engine for cookiecutter-qa and pytest-play packages Feb 18, 2019 4 - Beta pytest (>=3.0.7) -`pytest-pyppeteer `_ A plugin to run pyppeteer in pytest. Nov 27, 2020 4 - Beta pytest (>=6.0.2) +`pytest-pyppeteer `_ A plugin to run pyppeteer in pytest. Feb 16, 2021 4 - Beta pytest (>=6.0.2) `pytest-pyq `_ Pytest fixture "q" for pyq Mar 10, 2020 5 - Production/Stable N/A `pytest-pyramid `_ pytest pyramid providing basic fixtures for testing pyramid applications with pytest test suite Jun 05, 2020 4 - Beta pytest `pytest-pyramid-server `_ Pyramid server fixture for py.test May 28, 2019 5 - Production/Stable pytest @@ -619,13 +621,13 @@ name `pytest-reportlog `_ Replacement for the --resultlog option, focused in simplicity and extensibility Dec 11, 2020 3 - Alpha pytest (>=5.2) `pytest-report-me `_ A pytest plugin to generate report. Dec 31, 2020 N/A pytest `pytest-report-parameters `_ pytest plugin for adding tests' parameters to junit report Jun 18, 2020 3 - Alpha pytest (>=2.4.2) -`pytest-reportportal `_ Agent for Reporting results of tests to the Report Portal Dec 14, 2020 N/A pytest (>=3.0.7) +`pytest-reportportal `_ Agent for Reporting results of tests to the Report Portal Feb 15, 2021 N/A pytest (>=3.0.7) `pytest-reqs `_ pytest plugin to check pinned requirements May 12, 2019 N/A pytest (>=2.4.2) `pytest-requests `_ A simple plugin to use with pytest Jun 24, 2019 4 - Beta pytest (>=3.5.0) `pytest-reraise `_ Make multi-threaded pytest test cases fail when they should Jun 03, 2020 5 - Production/Stable N/A `pytest-rerun `_ Re-run only changed files in specified branch Jul 08, 2019 N/A pytest (>=3.6) `pytest-rerunfailures `_ pytest plugin to re-run tests to eliminate flaky failures Sep 29, 2020 5 - Production/Stable pytest (>=5.0) -`pytest-resilient-circuits `_ Resilient Circuits fixtures for PyTest. Jan 21, 2021 N/A N/A +`pytest-resilient-circuits `_ Resilient Circuits fixtures for PyTest. Feb 19, 2021 N/A N/A `pytest-resource `_ Load resource fixture plugin to use with pytest Nov 14, 2018 4 - Beta N/A `pytest-resource-path `_ Provides path for uniform access to test resources in isolated directory Aug 18, 2020 5 - Production/Stable pytest (>=3.5.0) `pytest-responsemock `_ Simplified requests calls mocking for pytest Oct 10, 2020 5 - Production/Stable N/A @@ -639,30 +641,30 @@ name `pytest-rotest `_ Pytest integration with rotest Sep 08, 2019 N/A pytest (>=3.5.0) `pytest-rpc `_ Extend py.test for RPC OpenStack testing. Feb 22, 2019 4 - Beta pytest (~=3.6) `pytest-rt `_ pytest data collector plugin for Testgr Jan 24, 2021 N/A N/A -`pytest-rts `_ Coverage-based regression test selection (RTS) plugin for pytest Jan 29, 2021 N/A pytest +`pytest-rts `_ Coverage-based regression test selection (RTS) plugin for pytest Feb 15, 2021 N/A pytest `pytest-runfailed `_ implement a --failed option for pytest Mar 24, 2016 N/A N/A `pytest-runner `_ Invoke py.test as distutils command with dependency resolution Feb 12, 2021 5 - Production/Stable pytest (!=3.7.3,>=3.5) ; extra == 'testing' `pytest-salt `_ Pytest Salt Plugin Jan 27, 2020 4 - Beta N/A `pytest-salt-containers `_ A Pytest plugin that builds and creates docker containers Nov 09, 2016 4 - Beta N/A -`pytest-salt-factories `_ Pytest Salt Plugin Jan 19, 2021 4 - Beta pytest (>=6.1.1) +`pytest-salt-factories `_ Pytest Salt Plugin Feb 19, 2021 4 - Beta pytest (>=6.1.1) `pytest-salt-from-filenames `_ Simple PyTest Plugin For Salt's Test Suite Specifically Jan 29, 2019 4 - Beta pytest (>=4.1) `pytest-salt-runtests-bridge `_ Simple PyTest Plugin For Salt's Test Suite Specifically Dec 05, 2019 4 - Beta pytest (>=4.1) `pytest-sanic `_ a pytest plugin for Sanic Sep 24, 2020 N/A pytest (>=5.2) `pytest-sanity `_ Dec 07, 2020 N/A N/A `pytest-sa-pg `_ May 14, 2019 N/A N/A -`pytest-sbase `_ A complete web automation framework for end-to-end testing. Feb 13, 2021 5 - Production/Stable N/A +`pytest-sbase `_ A complete web automation framework for end-to-end testing. Feb 19, 2021 5 - Production/Stable N/A `pytest-scenario `_ pytest plugin for test scenarios Feb 06, 2017 3 - Alpha N/A `pytest-schema `_ 👍 Validate return values against a schema-like object in testing Aug 31, 2020 5 - Production/Stable pytest (>=3.5.0) `pytest-securestore `_ An encrypted password store for use within pytest cases Jun 19, 2019 4 - Beta N/A `pytest-select `_ A pytest plugin which allows to (de-)select tests from a file. Jan 18, 2019 3 - Alpha pytest (>=3.0) `pytest-selenium `_ pytest plugin for Selenium Sep 19, 2020 5 - Production/Stable pytest (>=5.0.0) -`pytest-seleniumbase `_ A complete web automation framework for end-to-end testing. Feb 13, 2021 5 - Production/Stable N/A +`pytest-seleniumbase `_ A complete web automation framework for end-to-end testing. Feb 19, 2021 5 - Production/Stable N/A `pytest-selenium-enhancer `_ pytest plugin for Selenium Nov 26, 2020 5 - Production/Stable N/A `pytest-selenium-pdiff `_ A pytest package implementing perceptualdiff for Selenium tests. Apr 06, 2017 2 - Pre-Alpha N/A `pytest-send-email `_ Send pytest execution result email Dec 04, 2019 N/A N/A `pytest-sentry `_ A pytest plugin to send testrun information to Sentry.io Dec 16, 2020 N/A N/A `pytest-server-fixtures `_ Extensible server fixures for py.test May 28, 2019 5 - Production/Stable pytest -`pytest-serverless `_ Automatically mocks resources from serverless.yml in pytest using moto. Feb 11, 2021 4 - Beta N/A +`pytest-serverless `_ Automatically mocks resources from serverless.yml in pytest using moto. Feb 20, 2021 4 - Beta N/A `pytest-services `_ Services plugin for pytest testing framework Oct 30, 2020 6 - Mature N/A `pytest-session2file `_ pytest-session2file (aka: pytest-session_to_file for v0.1.0 - v0.1.2) is a py.test plugin for capturing and saving to file the stdout of py.test. Jan 26, 2021 3 - Alpha pytest `pytest-session-fixture-globalize `_ py.test plugin to make session fixtures behave as if written in conftest, even if it is written in some modules May 15, 2018 4 - Beta N/A @@ -682,6 +684,7 @@ name `pytest-slack `_ Pytest to Slack reporting plugin Dec 15, 2020 5 - Production/Stable N/A `pytest-smartcollect `_ A plugin for collecting tests that touch changed code Oct 04, 2018 N/A pytest (>=3.5.0) `pytest-smartcov `_ Smart coverage plugin for pytest. Sep 30, 2017 3 - Alpha N/A +`pytest-smtp `_ Send email with pytest execution result Feb 20, 2021 N/A pytest `pytest-snail `_ Plugin for adding a marker to slow running tests. 🐌 Nov 04, 2019 3 - Alpha pytest (>=5.0.1) `pytest-snapci `_ py.test plugin for Snap-CI Nov 12, 2015 N/A N/A `pytest-snapshot `_ A plugin to enable snapshot testing with pytest. Jan 22, 2021 4 - Beta pytest (>=3.0.0) @@ -706,6 +709,7 @@ name `pytest-sqitch `_ sqitch for pytest Apr 06, 2020 4 - Beta N/A `pytest-sqlalchemy `_ pytest plugin with sqlalchemy related fixtures Mar 13, 2018 3 - Alpha N/A `pytest-sql-bigquery `_ Yet another SQL-testing framework for BigQuery provided by pytest plugin Dec 19, 2019 N/A pytest +`pytest-srcpaths `_ Add paths to sys.path Feb 18, 2021 N/A N/A `pytest-ssh `_ pytest plugin for ssh command run May 27, 2019 N/A pytest `pytest-start-from `_ Start pytest run from a given point Apr 11, 2016 N/A N/A `pytest-statsd `_ pytest plugin for reporting to graphite Nov 30, 2018 5 - Production/Stable pytest (>=3.0.0) From c9bb4c418f889820f786cbf8e63cbc057c653399 Mon Sep 17 00:00:00 2001 From: Matthew Hughes Date: Sun, 21 Feb 2021 13:10:00 +0000 Subject: [PATCH 0142/2772] fixup! Rename variables 'tmpdir'->'tmp_path' * Add some more of these * Also reintroduce+rename instances of fixture usages that were 'tmpdir'->'tmp_path' --- testing/python/collect.py | 2 +- testing/python/integration.py | 2 +- testing/test_conftest.py | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/testing/python/collect.py b/testing/python/collect.py index 22ac6464b89..4256851e254 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -274,7 +274,7 @@ def test_function_as_object_instance_ignored(self, pytester: Pytester) -> None: pytester.makepyfile( """ class A(object): - def __call__(self): + def __call__(self, tmp_path): 0/0 test_a = A() diff --git a/testing/python/integration.py b/testing/python/integration.py index 77ebfa23ef2..1ab2149ff07 100644 --- a/testing/python/integration.py +++ b/testing/python/integration.py @@ -234,7 +234,7 @@ def mock_basename(path): @mock.patch("os.path.abspath") @mock.patch("os.path.normpath") @mock.patch("os.path.basename", new=mock_basename) - def test_someting(normpath, abspath): + def test_someting(normpath, abspath, tmp_path): abspath.return_value = "this" os.path.normpath(os.path.abspath("hello")) normpath.assert_any_call("this") diff --git a/testing/test_conftest.py b/testing/test_conftest.py index 80f2a6d0bc0..3497b7cc4fd 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -44,15 +44,15 @@ class TestConftestValueAccessGlobal: def basedir( self, request, tmp_path_factory: TempPathFactory ) -> Generator[Path, None, None]: - tmpdir = tmp_path_factory.mktemp("basedir", numbered=True) - tmpdir.joinpath("adir/b").mkdir(parents=True) - tmpdir.joinpath("adir/conftest.py").write_text("a=1 ; Directory = 3") - tmpdir.joinpath("adir/b/conftest.py").write_text("b=2 ; a = 1.5") + tmp_path = tmp_path_factory.mktemp("basedir", numbered=True) + tmp_path.joinpath("adir/b").mkdir(parents=True) + tmp_path.joinpath("adir/conftest.py").write_text("a=1 ; Directory = 3") + tmp_path.joinpath("adir/b/conftest.py").write_text("b=2 ; a = 1.5") if request.param == "inpackage": - tmpdir.joinpath("adir/__init__.py").touch() - tmpdir.joinpath("adir/b/__init__.py").touch() + tmp_path.joinpath("adir/__init__.py").touch() + tmp_path.joinpath("adir/b/__init__.py").touch() - yield tmpdir + yield tmp_path def test_basic_init(self, basedir: Path) -> None: conftest = PytestPluginManager() From 060cbef2604c7f0dd5122aca0e0cdd77c5b2c01f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Feb 2021 03:01:27 +0000 Subject: [PATCH 0143/2772] build(deps): bump django in /testing/plugins_integration Bumps [django](https://github.com/django/django) from 3.1.6 to 3.1.7. - [Release notes](https://github.com/django/django/releases) - [Commits](https://github.com/django/django/compare/3.1.6...3.1.7) Signed-off-by: dependabot[bot] --- testing/plugins_integration/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt index 6460e838535..730d1f028fe 100644 --- a/testing/plugins_integration/requirements.txt +++ b/testing/plugins_integration/requirements.txt @@ -1,5 +1,5 @@ anyio[curio,trio]==2.1.0 -django==3.1.6 +django==3.1.7 pytest-asyncio==0.14.0 pytest-bdd==4.0.2 pytest-cov==2.11.1 From e503e27579cb7a8bc7bb6efb252e6aada5a17ee0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 22 Feb 2021 17:04:30 +0000 Subject: [PATCH 0144/2772] [pre-commit.ci] pre-commit autoupdate --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6b2d09e814e..fed7ca83cbb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -47,7 +47,7 @@ repos: hooks: - id: python-use-type-annotations - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.800 + rev: v0.812 hooks: - id: mypy files: ^(src/|testing/) From 54a154c86f4806327081b80193cebca7934468d0 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 23 Feb 2021 17:56:42 +0100 Subject: [PATCH 0145/2772] Allow Class.from_parent to forward custom parameters to the constructor Similarly to #7143, at work we have a project with a custom pytest.Class subclass, adding an additional argument to the constructor. All from_parent implementations in pytest accept and forward *kw, except Class (before this change) and DoctestItem - since I'm not familiar with doctest support, I've left the latter as-is. --- changelog/8367.bugfix.rst | 1 + src/_pytest/python.py | 4 ++-- testing/test_collection.py | 18 ++++++++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 changelog/8367.bugfix.rst diff --git a/changelog/8367.bugfix.rst b/changelog/8367.bugfix.rst new file mode 100644 index 00000000000..f4b03670108 --- /dev/null +++ b/changelog/8367.bugfix.rst @@ -0,0 +1 @@ +Fix ``Class.from_parent`` so it forwards extra keyword arguments to the constructor. diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 726241cb5b9..944c395a84d 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -763,9 +763,9 @@ class Class(PyCollector): """Collector for test methods.""" @classmethod - def from_parent(cls, parent, *, name, obj=None): + def from_parent(cls, parent, *, name, obj=None, **kw): """The public constructor.""" - return super().from_parent(name=name, parent=parent) + return super().from_parent(name=name, parent=parent, **kw) def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: if not safe_getattr(self.obj, "__test__", True): diff --git a/testing/test_collection.py b/testing/test_collection.py index 3dd9283eced..39538ae98cc 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -1360,6 +1360,24 @@ def from_parent(cls, parent, *, fspath, x): assert collector.x == 10 +def test_class_from_parent(pytester: Pytester, request: FixtureRequest) -> None: + """Ensure Class.from_parent can forward custom arguments to the constructor.""" + + class MyCollector(pytest.Class): + def __init__(self, name, parent, x): + super().__init__(name, parent) + self.x = x + + @classmethod + def from_parent(cls, parent, *, name, x): + return super().from_parent(parent=parent, name=name, x=x) + + collector = MyCollector.from_parent( + parent=request.session, name="foo", x=10 + ) + assert collector.x == 10 + + class TestImportModeImportlib: def test_collect_duplicate_names(self, pytester: Pytester) -> None: """--import-mode=importlib can import modules with same names that are not in packages.""" From 3b7fc2c9c839148d19518af655a1d347351286b0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 23 Feb 2021 17:02:45 +0000 Subject: [PATCH 0146/2772] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- testing/test_collection.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/testing/test_collection.py b/testing/test_collection.py index 39538ae98cc..298c2dde1a0 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -1372,9 +1372,7 @@ def __init__(self, name, parent, x): def from_parent(cls, parent, *, name, x): return super().from_parent(parent=parent, name=name, x=x) - collector = MyCollector.from_parent( - parent=request.session, name="foo", x=10 - ) + collector = MyCollector.from_parent(parent=request.session, name="foo", x=10) assert collector.x == 10 From 9d09d1991186d842dce4dc2ea023996b3c091fc8 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 23 Feb 2021 18:03:10 +0100 Subject: [PATCH 0147/2772] Fix typo in changelog See #7143 --- doc/en/changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index 3e854f59971..967fca2098a 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -1028,7 +1028,7 @@ Bug Fixes - `#7110 `_: Fixed regression: ``asyncbase.TestCase`` tests are executed correctly again. -- `#7143 `_: Fix ``File.from_constructor`` so it forwards extra keyword arguments to the constructor. +- `#7143 `_: Fix ``File.from_parent`` so it forwards extra keyword arguments to the constructor. - `#7145 `_: Classes with broken ``__getattribute__`` methods are displayed correctly during failures. From 514f8e068080b374b470f6c9f349c48edfc0d3d8 Mon Sep 17 00:00:00 2001 From: Matthew Hughes Date: Wed, 24 Feb 2021 20:55:35 +0000 Subject: [PATCH 0148/2772] fixup! Rename variables 'tmpdir'->'tmp_path' --- testing/test_collection.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/testing/test_collection.py b/testing/test_collection.py index cf34ef118ca..9060ef36c2d 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -136,7 +136,7 @@ def test_ignored_certain_directories(self, pytester: Pytester) -> None: ensure_file(tmp_path / ".whatever" / "test_notfound.py") ensure_file(tmp_path / ".bzr" / "test_notfound.py") ensure_file(tmp_path / "normal" / "test_found.py") - for x in Path(str(tmp_path)).rglob("test_*.py"): + for x in tmp_path.rglob("test_*.py"): x.write_text("def test_hello(): pass", "utf-8") result = pytester.runpytest("--collect-only") @@ -632,8 +632,7 @@ class Test_getinitialnodes: def test_global_file(self, pytester: Pytester) -> None: tmp_path = pytester.path x = ensure_file(tmp_path / "x.py") - with tmp_path.cwd(): - config = pytester.parseconfigure(x) + config = pytester.parseconfigure(x) col = pytester.getnode(config, x) assert isinstance(col, pytest.Module) assert col.name == "x.py" From b7f2d7ca61d6169495e5780fffc252daaacd6583 Mon Sep 17 00:00:00 2001 From: Simon K Date: Thu, 25 Feb 2021 08:28:57 +0000 Subject: [PATCH 0149/2772] Fixed an issue where `getpass.getuser()` contained illegal characters for file directories (#8365) * retry writing pytest-of dir when invalid chars are in directory name * add unit tests for getbasetemp() and changelog * patch _basetemp & _given_basetemp for testing basetemp() * Tweak changelog for #8317, tidy up comments --- changelog/8317.bugfix.rst | 1 + src/_pytest/tmpdir.py | 7 ++++++- testing/test_tmpdir.py | 12 ++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 changelog/8317.bugfix.rst diff --git a/changelog/8317.bugfix.rst b/changelog/8317.bugfix.rst new file mode 100644 index 00000000000..7312880a11f --- /dev/null +++ b/changelog/8317.bugfix.rst @@ -0,0 +1 @@ +Fixed an issue where illegal directory characters derived from ``getpass.getuser()`` raised an ``OSError``. diff --git a/src/_pytest/tmpdir.py b/src/_pytest/tmpdir.py index 29c7e19d7b4..47729ae5fee 100644 --- a/src/_pytest/tmpdir.py +++ b/src/_pytest/tmpdir.py @@ -115,7 +115,12 @@ def getbasetemp(self) -> Path: # use a sub-directory in the temproot to speed-up # make_numbered_dir() call rootdir = temproot.joinpath(f"pytest-of-{user}") - rootdir.mkdir(exist_ok=True) + try: + rootdir.mkdir(exist_ok=True) + except OSError: + # getuser() likely returned illegal characters for the platform, use unknown back off mechanism + rootdir = temproot.joinpath("pytest-of-unknown") + rootdir.mkdir(exist_ok=True) basetemp = make_numbered_dir_with_cleanup( prefix="pytest-", root=rootdir, keep=3, lock_timeout=LOCK_TIMEOUT ) diff --git a/testing/test_tmpdir.py b/testing/test_tmpdir.py index d123287aa38..4dec9c59a3c 100644 --- a/testing/test_tmpdir.py +++ b/testing/test_tmpdir.py @@ -11,6 +11,7 @@ import pytest from _pytest import pathlib from _pytest.config import Config +from _pytest.monkeypatch import MonkeyPatch from _pytest.pathlib import cleanup_numbered_dir from _pytest.pathlib import create_cleanup_lock from _pytest.pathlib import make_numbered_dir @@ -445,3 +446,14 @@ def test(tmp_path): # running a second time and ensure we don't crash result = pytester.runpytest("--basetemp=tmp") assert result.ret == 0 + + +def test_tmp_path_factory_handles_invalid_dir_characters( + tmp_path_factory: TempPathFactory, monkeypatch: MonkeyPatch +) -> None: + monkeypatch.setattr("getpass.getuser", lambda: "os/<:*?;>agnostic") + # _basetemp / _given_basetemp are cached / set in parallel runs, patch them + monkeypatch.setattr(tmp_path_factory, "_basetemp", None) + monkeypatch.setattr(tmp_path_factory, "_given_basetemp", None) + p = tmp_path_factory.getbasetemp() + assert "pytest-of-unknown" in str(p) From 22c0dace3b67ac2aaf8d45f6f73ed9838c30e8eb Mon Sep 17 00:00:00 2001 From: Simon K Date: Thu, 25 Feb 2021 20:32:27 +0000 Subject: [PATCH 0150/2772] change istestfunction to callable() (#8374) --- src/_pytest/python.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 944c395a84d..40116ab9c5a 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -384,10 +384,7 @@ def istestfunction(self, obj: object, name: str) -> bool: if isinstance(obj, staticmethod): # staticmethods need to be unwrapped. obj = safe_getattr(obj, "__func__", False) - return ( - safe_getattr(obj, "__call__", False) - and fixtures.getfixturemarker(obj) is None - ) + return callable(obj) and fixtures.getfixturemarker(obj) is None else: return False From a623b1b0861719a48d89f3ee7ac18250b7ea06ca Mon Sep 17 00:00:00 2001 From: pytest bot Date: Sun, 28 Feb 2021 00:48:46 +0000 Subject: [PATCH 0151/2772] [automated] Update plugin list --- doc/en/plugin_list.rst | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/doc/en/plugin_list.rst b/doc/en/plugin_list.rst index 5d93204bcd4..f50b6033ff1 100644 --- a/doc/en/plugin_list.rst +++ b/doc/en/plugin_list.rst @@ -3,7 +3,7 @@ Plugins List PyPI projects that match "pytest-\*" are considered plugins and are listed automatically. Packages classified as inactive are excluded. -This list contains 831 plugins. +This list contains 833 plugins. ============================================================================================================== ======================================================================================================================================================================== ============== ===================== ============================================ name summary last release status requires @@ -11,6 +11,7 @@ name `pytest-adaptavist `_ pytest plugin for generating test execution results within Jira Test Management (tm4j) Feb 05, 2020 N/A pytest (>=3.4.1) `pytest-adf `_ Pytest plugin for writing Azure Data Factory integration tests Jun 03, 2020 4 - Beta pytest (>=3.5.0) `pytest-aggreport `_ pytest plugin for pytest-repeat that generate aggregate report of the same test cases with additional statistics details. Jul 19, 2019 4 - Beta pytest (>=4.3.1) +`pytest-aio `_ Pytest plugin for testing async python code Feb 27, 2021 4 - Beta pytest ; extra == 'tests' `pytest-aiofiles `_ pytest fixtures for writing aiofiles tests with pyfakefs May 14, 2017 5 - Production/Stable N/A `pytest-aiohttp `_ pytest plugin for aiohttp support Dec 05, 2017 N/A pytest `pytest-aiohttp-client `_ Pytest `client` fixture for the Aiohttp Nov 01, 2020 N/A pytest (>=6) @@ -59,7 +60,7 @@ name `pytest-aws `_ pytest plugin for testing AWS resource configurations Oct 04, 2017 4 - Beta N/A `pytest-axe `_ pytest plugin for axe-selenium-python Nov 12, 2018 N/A pytest (>=3.0.0) `pytest-azurepipelines `_ Formatting PyTest output for Azure Pipelines UI Jul 23, 2020 4 - Beta pytest (>=3.5.0) -`pytest-bandit `_ A bandit plugin for pytest Sep 25, 2019 4 - Beta pytest (>=3.5.0) +`pytest-bandit `_ A bandit plugin for pytest Feb 23, 2021 4 - Beta pytest (>=3.5.0) `pytest-base-url `_ pytest plugin for URL based testing Jun 19, 2020 5 - Production/Stable pytest (>=2.7.3) `pytest-bdd `_ BDD for pytest Dec 07, 2020 6 - Mature pytest (>=4.3) `pytest-bdd-splinter `_ Common steps for pytest bdd and splinter integration Aug 12, 2019 5 - Production/Stable pytest (>=4.0.0) @@ -105,7 +106,7 @@ name `pytest-change-report `_ turn . into √,turn F into x Sep 14, 2020 N/A pytest `pytest-chdir `_ A pytest fixture for changing current working directory Jan 28, 2020 N/A pytest (>=5.0.0,<6.0.0) `pytest-check `_ A pytest plugin that allows multiple failures per test. Dec 27, 2020 5 - Production/Stable N/A -`pytest-checkdocs `_ check the README when running tests Jan 01, 2021 5 - Production/Stable pytest (!=3.7.3,>=3.5) ; extra == 'testing' +`pytest-checkdocs `_ check the README when running tests Feb 27, 2021 5 - Production/Stable pytest (>=4.6) ; extra == 'testing' `pytest-checkipdb `_ plugin to check if there are ipdb debugs left Jul 22, 2020 5 - Production/Stable pytest (>=2.9.2) `pytest-check-links `_ Check links in files Jul 29, 2020 N/A N/A `pytest-check-mk `_ pytest plugin to test Check_MK checks Nov 19, 2015 4 - Beta pytest @@ -152,6 +153,7 @@ name `pytest-custom-concurrency `_ Custom grouping concurrence for pytest Feb 08, 2021 N/A N/A `pytest-custom-exit-code `_ Exit pytest test session with custom exit code in different scenarios Aug 07, 2019 4 - Beta pytest (>=4.0.2) `pytest-custom-report `_ Configure the symbols displayed for test outcomes Jan 30, 2019 N/A pytest +`pytest-custom-scheduling `_ Custom grouping for pytest-xdist Feb 22, 2021 N/A N/A `pytest-cython `_ A plugin for testing Cython extension modules Jan 26, 2021 4 - Beta pytest (>=2.7.3) `pytest-darker `_ A pytest plugin for checking of modified code using Darker Aug 16, 2020 N/A pytest (>=6.0.1) ; extra == 'test' `pytest-dash `_ pytest fixtures to run dash applications. Mar 18, 2019 N/A N/A @@ -298,7 +300,7 @@ name `pytest-flakefinder `_ Runs tests multiple times to expose flakiness. Jul 28, 2020 4 - Beta pytest (>=2.7.1) `pytest-flakes `_ pytest plugin to check source code with pyflakes Nov 28, 2020 5 - Production/Stable N/A `pytest-flaptastic `_ Flaptastic py.test plugin Mar 17, 2019 N/A N/A -`pytest-flask `_ A set of py.test fixtures to test Flask applications. Nov 09, 2020 5 - Production/Stable pytest (>=5.2) +`pytest-flask `_ A set of py.test fixtures to test Flask applications. Feb 27, 2021 5 - Production/Stable pytest (>=5.2) `pytest-flask-sqlalchemy `_ A pytest plugin for preserving test isolation in Flask-SQlAlchemy using database transactions. Apr 04, 2019 4 - Beta pytest (>=3.2.1) `pytest-flask-sqlalchemy-transactions `_ Run tests in transactions using pytest, Flask, and SQLalchemy. Aug 02, 2018 4 - Beta pytest (>=3.2.1) `pytest-focus `_ A pytest plugin that alerts user of failed test cases with screen notifications May 04, 2019 4 - Beta pytest @@ -316,14 +318,14 @@ name `pytest-gevent `_ Ensure that gevent is properly patched when invoking pytest Feb 25, 2020 N/A pytest `pytest-gherkin `_ A flexible framework for executing BDD gherkin tests Jul 27, 2019 3 - Alpha pytest (>=5.0.0) `pytest-ghostinspector `_ For finding/executing Ghost Inspector tests May 17, 2016 3 - Alpha N/A -`pytest-girder `_ A set of pytest fixtures for testing Girder applications. Feb 11, 2021 N/A N/A +`pytest-girder `_ A set of pytest fixtures for testing Girder applications. Feb 25, 2021 N/A N/A `pytest-git `_ Git repository fixture for py.test May 28, 2019 5 - Production/Stable pytest `pytest-gitcov `_ Pytest plugin for reporting on coverage of the last git commit. Jan 11, 2020 2 - Pre-Alpha N/A `pytest-git-fixtures `_ Pytest fixtures for testing with git. Jan 25, 2021 4 - Beta pytest `pytest-github `_ Plugin for py.test that associates tests with github issues using a marker. Mar 07, 2019 5 - Production/Stable N/A `pytest-github-actions-annotate-failures `_ pytest plugin to annotate failed tests with a workflow command for GitHub Actions Oct 13, 2020 N/A pytest (>=4.0.0) `pytest-gitignore `_ py.test plugin to ignore the same files as git Jul 17, 2015 4 - Beta N/A -`pytest-gnupg-fixtures `_ Pytest fixtures for testing with gnupg. Jan 12, 2021 4 - Beta pytest +`pytest-gnupg-fixtures `_ Pytest fixtures for testing with gnupg. Feb 22, 2021 4 - Beta pytest `pytest-golden `_ Plugin for pytest that offloads expected outputs to data files Nov 23, 2020 N/A pytest (>=6.1.2,<7.0.0) `pytest-graphql-schema `_ Get graphql schema as fixture for pytest Oct 18, 2019 N/A N/A `pytest-greendots `_ Green progress dots Feb 08, 2014 3 - Alpha N/A @@ -356,7 +358,7 @@ name `pytest-httpx `_ Send responses to httpx. Nov 25, 2020 5 - Production/Stable pytest (==6.*) `pytest-hue `_ Visualise PyTest status via your Phillips Hue lights May 09, 2019 N/A N/A `pytest-hypo-25 `_ help hypo module for pytest Jan 12, 2020 3 - Alpha N/A -`pytest-ibutsu `_ A plugin to sent pytest results to an Ibutsu server Feb 11, 2021 4 - Beta pytest +`pytest-ibutsu `_ A plugin to sent pytest results to an Ibutsu server Feb 24, 2021 4 - Beta pytest `pytest-icdiff `_ use icdiff for better error messages in pytest assertions Apr 08, 2020 4 - Beta N/A `pytest-idapro `_ A pytest plugin for idapython. Allows a pytest setup to run tests outside and inside IDA in an automated manner by runnig pytest inside IDA and by mocking idapython api Nov 03, 2018 N/A N/A `pytest-ignore-flaky `_ ignore failures from flaky tests (pytest plugin) Jan 14, 2019 5 - Production/Stable pytest (>=3.7) @@ -369,7 +371,7 @@ name `pytest-inmanta `_ A py.test plugin providing fixtures to simplify inmanta modules testing. Oct 12, 2020 5 - Production/Stable N/A `pytest-inmanta-extensions `_ Inmanta tests package Jan 07, 2021 5 - Production/Stable N/A `pytest-Inomaly `_ A simple image diff plugin for pytest Feb 13, 2018 4 - Beta N/A -`pytest-insta `_ A practical snapshot testing plugin for pytest Nov 29, 2020 N/A pytest (>=6.0.2,<7.0.0) +`pytest-insta `_ A practical snapshot testing plugin for pytest Feb 25, 2021 N/A pytest (>=6.0.2,<7.0.0) `pytest-instafail `_ pytest plugin to show failures instantly Jun 14, 2020 4 - Beta pytest (>=2.9) `pytest-instrument `_ pytest plugin to instrument tests Apr 05, 2020 5 - Production/Stable pytest (>=5.1.0) `pytest-integration `_ Organizing pytests by integration or not Apr 16, 2020 N/A N/A @@ -417,7 +419,7 @@ name `pytest-localserver `_ py.test plugin to test server connections locally. Nov 14, 2018 4 - Beta N/A `pytest-localstack `_ Pytest plugin for AWS integration tests Aug 22, 2019 4 - Beta pytest (>=3.3.0) `pytest-lockable `_ lockable resource plugin for pytest Oct 05, 2020 3 - Alpha pytest -`pytest-locker `_ Used to lock object during testing. Essentially changing assertions from being hard coded to asserting that nothing changed Aug 11, 2020 N/A pytest (>=5.4) +`pytest-locker `_ Used to lock object during testing. Essentially changing assertions from being hard coded to asserting that nothing changed Feb 25, 2021 N/A pytest (>=5.4) `pytest-logbook `_ py.test plugin to capture logbook log messages Nov 23, 2015 5 - Production/Stable pytest (>=2.8) `pytest-logfest `_ Pytest plugin providing three logger fixtures with basic or full writing to log files Jul 21, 2019 4 - Beta pytest (>=3.5.0) `pytest-logger `_ Plugin configuring handlers for loggers from Python logging module. Jul 25, 2019 4 - Beta pytest (>=3.2) @@ -503,7 +505,7 @@ name `pytest-only `_ Use @pytest.mark.only to run a single test Jan 19, 2020 N/A N/A `pytest-oot `_ Run object-oriented tests in a simple format Sep 18, 2016 4 - Beta N/A `pytest-openfiles `_ Pytest plugin for detecting inadvertent open file handles Apr 16, 2020 3 - Alpha pytest (>=4.6) -`pytest-opentmi `_ pytest plugin for publish results to opentmi Jun 10, 2020 5 - Production/Stable pytest (>=5.0) +`pytest-opentmi `_ pytest plugin for publish results to opentmi Feb 26, 2021 5 - Production/Stable pytest (>=5.0) `pytest-operator `_ Fixtures for Operators Feb 20, 2021 N/A N/A `pytest-optional `_ include/exclude values of fixtures in pytest Oct 07, 2015 N/A N/A `pytest-optional-tests `_ Easy declaration of optional tests (i.e., that are not run by default) Jul 09, 2019 4 - Beta pytest (>=4.5.0) @@ -540,7 +542,7 @@ name `pytest-platform-markers `_ Markers for pytest to skip tests on specific platforms Sep 09, 2019 4 - Beta pytest (>=3.6.0) `pytest-play `_ pytest plugin that let you automate actions and assertions with test metrics reporting executing plain YAML files Jun 12, 2019 5 - Production/Stable N/A `pytest-playbook `_ Pytest plugin for reading playbooks. Jan 21, 2021 3 - Alpha pytest (>=6.1.2,<7.0.0) -`pytest-playwright `_ A pytest wrapper with fixtures for Playwright to automate web browsers Jan 21, 2021 N/A pytest +`pytest-playwright `_ A pytest wrapper with fixtures for Playwright to automate web browsers Feb 25, 2021 N/A pytest `pytest-plt `_ Fixtures for quickly making Matplotlib plots in tests Aug 17, 2020 5 - Production/Stable pytest `pytest-plugin-helpers `_ A plugin to help developing and testing other plugins Nov 23, 2019 4 - Beta pytest (>=3.5.0) `pytest-plus `_ PyTest Plus Plugin :: extends pytest functionality Mar 19, 2020 5 - Production/Stable pytest (>=3.50) @@ -555,7 +557,7 @@ name `pytest-pop `_ A pytest plugin to help with testing pop projects Aug 13, 2020 5 - Production/Stable pytest (>=5.4.0) `pytest-portion `_ Select a portion of the collected tests Jan 28, 2021 4 - Beta pytest (>=3.5.0) `pytest-postgres `_ Run PostgreSQL in Docker container in Pytest. Mar 22, 2020 N/A pytest -`pytest-postgresql `_ Postgresql fixtures and fixture factories for Pytest. Feb 11, 2021 5 - Production/Stable pytest (>=3.0.0) +`pytest-postgresql `_ Postgresql fixtures and fixture factories for Pytest. Feb 23, 2021 5 - Production/Stable pytest (>=3.0.0) `pytest-power `_ pytest plugin with powerful fixtures Dec 31, 2020 N/A pytest (>=5.4) `pytest-pride `_ Minitest-style test colors Apr 02, 2016 3 - Alpha N/A `pytest-print `_ pytest-print adds the printer fixture you can use to print messages to the user (directly to the pytest runner, not stdout) Oct 23, 2020 5 - Production/Stable pytest (>=3.0.0) @@ -575,7 +577,7 @@ name `pytest-pypom-navigation `_ Core engine for cookiecutter-qa and pytest-play packages Feb 18, 2019 4 - Beta pytest (>=3.0.7) `pytest-pyppeteer `_ A plugin to run pyppeteer in pytest. Feb 16, 2021 4 - Beta pytest (>=6.0.2) `pytest-pyq `_ Pytest fixture "q" for pyq Mar 10, 2020 5 - Production/Stable N/A -`pytest-pyramid `_ pytest pyramid providing basic fixtures for testing pyramid applications with pytest test suite Jun 05, 2020 4 - Beta pytest +`pytest-pyramid `_ pytest_pyramid - provides fixtures for testing pyramid applications with pytest test suite Feb 26, 2021 5 - Production/Stable pytest `pytest-pyramid-server `_ Pyramid server fixture for py.test May 28, 2019 5 - Production/Stable pytest `pytest-pytestrail `_ Pytest plugin for interaction with TestRail Aug 27, 2020 4 - Beta pytest (>=3.8.0) `pytest-pythonpath `_ pytest plugin for adding to the PYTHONPATH from command line or configs. Aug 22, 2018 5 - Production/Stable N/A @@ -649,16 +651,16 @@ name `pytest-salt-factories `_ Pytest Salt Plugin Feb 19, 2021 4 - Beta pytest (>=6.1.1) `pytest-salt-from-filenames `_ Simple PyTest Plugin For Salt's Test Suite Specifically Jan 29, 2019 4 - Beta pytest (>=4.1) `pytest-salt-runtests-bridge `_ Simple PyTest Plugin For Salt's Test Suite Specifically Dec 05, 2019 4 - Beta pytest (>=4.1) -`pytest-sanic `_ a pytest plugin for Sanic Sep 24, 2020 N/A pytest (>=5.2) +`pytest-sanic `_ a pytest plugin for Sanic Feb 27, 2021 N/A pytest (>=5.2) `pytest-sanity `_ Dec 07, 2020 N/A N/A `pytest-sa-pg `_ May 14, 2019 N/A N/A -`pytest-sbase `_ A complete web automation framework for end-to-end testing. Feb 19, 2021 5 - Production/Stable N/A +`pytest-sbase `_ A complete web automation framework for end-to-end testing. Feb 27, 2021 5 - Production/Stable N/A `pytest-scenario `_ pytest plugin for test scenarios Feb 06, 2017 3 - Alpha N/A `pytest-schema `_ 👍 Validate return values against a schema-like object in testing Aug 31, 2020 5 - Production/Stable pytest (>=3.5.0) `pytest-securestore `_ An encrypted password store for use within pytest cases Jun 19, 2019 4 - Beta N/A `pytest-select `_ A pytest plugin which allows to (de-)select tests from a file. Jan 18, 2019 3 - Alpha pytest (>=3.0) `pytest-selenium `_ pytest plugin for Selenium Sep 19, 2020 5 - Production/Stable pytest (>=5.0.0) -`pytest-seleniumbase `_ A complete web automation framework for end-to-end testing. Feb 19, 2021 5 - Production/Stable N/A +`pytest-seleniumbase `_ A complete web automation framework for end-to-end testing. Feb 27, 2021 5 - Production/Stable N/A `pytest-selenium-enhancer `_ pytest plugin for Selenium Nov 26, 2020 5 - Production/Stable N/A `pytest-selenium-pdiff `_ A pytest package implementing perceptualdiff for Selenium tests. Apr 06, 2017 2 - Pre-Alpha N/A `pytest-send-email `_ Send pytest execution result email Dec 04, 2019 N/A N/A @@ -703,7 +705,7 @@ name `pytest-split `_ Pytest plugin for splitting test suite based on test execution time Apr 07, 2020 1 - Planning N/A `pytest-splitio `_ Split.io SDK integration for e2e tests Sep 22, 2020 N/A pytest (<7,>=5.0) `pytest-split-tests `_ A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. Forked from Mark Adams' original project pytest-test-groups. May 28, 2019 N/A pytest (>=2.5) -`pytest-splunk-addon `_ A Dynamic test tool for Splunk Apps and Add-ons Feb 10, 2021 N/A pytest (>5.4.0,<6.1) +`pytest-splunk-addon `_ A Dynamic test tool for Splunk Apps and Add-ons Feb 26, 2021 N/A pytest (>5.4.0,<6.1) `pytest-splunk-addon-ui-smartx `_ Library to support testing Splunk Add-on UX Jan 18, 2021 N/A N/A `pytest-splunk-env `_ pytest fixtures for interaction with Splunk Enterprise and Splunk Cloud Oct 22, 2020 N/A pytest (>=6.1.1,<7.0.0) `pytest-sqitch `_ sqitch for pytest Apr 06, 2020 4 - Beta N/A @@ -778,7 +780,7 @@ name `pytest-tornado5 `_ A py.test plugin providing fixtures and markers to simplify testing of asynchronous tornado applications. Nov 16, 2018 5 - Production/Stable pytest (>=3.6) `pytest-tornado-yen3 `_ A py.test plugin providing fixtures and markers to simplify testing of asynchronous tornado applications. Oct 15, 2018 5 - Production/Stable N/A `pytest-tornasync `_ py.test plugin for testing Python 3.5+ Tornado code Jul 15, 2019 3 - Alpha pytest (>=3.0) -`pytest-track `_ Oct 23, 2020 3 - Alpha pytest (>=3.0) +`pytest-track `_ Feb 26, 2021 3 - Alpha pytest (>=3.0) `pytest-translations `_ Test your translation files. Oct 26, 2020 5 - Production/Stable N/A `pytest-travis-fold `_ Folds captured output sections in Travis CI build log Nov 29, 2017 4 - Beta pytest (>=2.6.0) `pytest-trello `_ Plugin for py.test that integrates trello using markers Nov 20, 2015 5 - Production/Stable N/A From 62ef87579647b7e97fba016f8300eb42d3fbe38d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Mar 2021 03:02:48 +0000 Subject: [PATCH 0152/2772] build(deps): bump anyio[curio,trio] in /testing/plugins_integration Bumps [anyio[curio,trio]](https://github.com/agronholm/anyio) from 2.1.0 to 2.2.0. - [Release notes](https://github.com/agronholm/anyio/releases) - [Changelog](https://github.com/agronholm/anyio/blob/master/docs/versionhistory.rst) - [Commits](https://github.com/agronholm/anyio/compare/2.1.0...2.2.0) Signed-off-by: dependabot[bot] --- testing/plugins_integration/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt index 730d1f028fe..712ac339ec1 100644 --- a/testing/plugins_integration/requirements.txt +++ b/testing/plugins_integration/requirements.txt @@ -1,4 +1,4 @@ -anyio[curio,trio]==2.1.0 +anyio[curio,trio]==2.2.0 django==3.1.7 pytest-asyncio==0.14.0 pytest-bdd==4.0.2 From 897b5a3bd6c4ede255770d9830ee119970bb1ea2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Mar 2021 03:02:50 +0000 Subject: [PATCH 0153/2772] build(deps): bump twisted in /testing/plugins_integration Bumps [twisted](https://github.com/twisted/twisted) from 20.3.0 to 21.2.0. - [Release notes](https://github.com/twisted/twisted/releases) - [Changelog](https://github.com/twisted/twisted/blob/twisted-21.2.0/NEWS.rst) - [Commits](https://github.com/twisted/twisted/compare/twisted-20.3.0...twisted-21.2.0) Signed-off-by: dependabot[bot] --- testing/plugins_integration/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt index 730d1f028fe..69011e651e5 100644 --- a/testing/plugins_integration/requirements.txt +++ b/testing/plugins_integration/requirements.txt @@ -11,5 +11,5 @@ pytest-rerunfailures==9.1.1 pytest-sugar==0.9.4 pytest-trio==0.7.0 pytest-twisted==1.13.2 -twisted==20.3.0 +twisted==21.2.0 pytest-xvfb==2.0.0 From decca74788d2f5d7b8fcac142579927346fc0a4b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 Mar 2021 16:53:28 +0000 Subject: [PATCH 0154/2772] [pre-commit.ci] pre-commit autoupdate --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fed7ca83cbb..b324b1f484e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -43,7 +43,7 @@ repos: hooks: - id: setup-cfg-fmt - repo: https://github.com/pre-commit/pygrep-hooks - rev: v1.7.1 + rev: v1.8.0 hooks: - id: python-use-type-annotations - repo: https://github.com/pre-commit/mirrors-mypy From c14a9adba35ac675ce3e825d34d01d5bb51748c3 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 4 Mar 2021 11:56:21 +0100 Subject: [PATCH 0155/2772] Fix skip signature (#8392) * Fix test_strict_and_skip The `--strict` argument was removed in #2552, but the removal wasn't actually correct - see #1472. * Fix argument handling in pytest.mark.skip See #8384 * Raise from None * Fix test name --- changelog/8384.bugfix.rst | 1 + doc/en/reference.rst | 2 +- src/_pytest/skipping.py | 13 +++++-------- testing/test_skipping.py | 18 +++++++++++++++++- 4 files changed, 24 insertions(+), 10 deletions(-) create mode 100644 changelog/8384.bugfix.rst diff --git a/changelog/8384.bugfix.rst b/changelog/8384.bugfix.rst new file mode 100644 index 00000000000..3b70987490e --- /dev/null +++ b/changelog/8384.bugfix.rst @@ -0,0 +1 @@ +The ``@pytest.mark.skip`` decorator now correctly handles its arguments. When the ``reason`` argument is accidentally given both positional and as a keyword (e.g. because it was confused with ``skipif``), a ``TypeError`` now occurs. Before, such tests were silently skipped, and the positional argument ignored. Additionally, ``reason`` is now documented correctly as positional or keyword (rather than keyword-only). diff --git a/doc/en/reference.rst b/doc/en/reference.rst index bc6c5670a5c..9ad82b3e4b9 100644 --- a/doc/en/reference.rst +++ b/doc/en/reference.rst @@ -150,7 +150,7 @@ pytest.mark.skip Unconditionally skip a test function. -.. py:function:: pytest.mark.skip(*, reason=None) +.. py:function:: pytest.mark.skip(reason=None) :keyword str reason: Reason why the test function is being skipped. diff --git a/src/_pytest/skipping.py b/src/_pytest/skipping.py index 1ad312919ca..7fe9783a4fa 100644 --- a/src/_pytest/skipping.py +++ b/src/_pytest/skipping.py @@ -161,7 +161,7 @@ def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool, class Skip: """The result of evaluate_skip_marks().""" - reason = attr.ib(type=str) + reason = attr.ib(type=str, default="unconditional skip") def evaluate_skip_marks(item: Item) -> Optional[Skip]: @@ -184,13 +184,10 @@ def evaluate_skip_marks(item: Item) -> Optional[Skip]: return Skip(reason) for mark in item.iter_markers(name="skip"): - if "reason" in mark.kwargs: - reason = mark.kwargs["reason"] - elif mark.args: - reason = mark.args[0] - else: - reason = "unconditional skip" - return Skip(reason) + try: + return Skip(*mark.args, **mark.kwargs) + except TypeError as e: + raise TypeError(str(e) + " - maybe you meant pytest.mark.skipif?") from None return None diff --git a/testing/test_skipping.py b/testing/test_skipping.py index fc66eb18e64..349de6e080f 100644 --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -861,9 +861,25 @@ def test_hello(): pass """ ) - result = pytester.runpytest("-rs") + result = pytester.runpytest("-rs", "--strict-markers") result.stdout.fnmatch_lines(["*unconditional skip*", "*1 skipped*"]) + def test_wrong_skip_usage(self, pytester: Pytester) -> None: + pytester.makepyfile( + """ + import pytest + @pytest.mark.skip(False, reason="I thought this was skipif") + def test_hello(): + pass + """ + ) + result = pytester.runpytest() + result.stdout.fnmatch_lines( + [ + "*TypeError: __init__() got multiple values for argument 'reason' - maybe you meant pytest.mark.skipif?" + ] + ) + class TestSkipif: def test_skipif_conditional(self, pytester: Pytester) -> None: From 19a2f7425ddec3b614da7c915e0cf8bb24b6906f Mon Sep 17 00:00:00 2001 From: Alexandros Tzannes Date: Thu, 4 Mar 2021 15:45:57 -0500 Subject: [PATCH 0156/2772] Merge pull request #8399 from atzannes/master closes #8394 Generated fixture names for unittest/xunit/nose should start with underscore --- changelog/8394.bugfix.rst | 1 + src/_pytest/python.py | 8 +++---- src/_pytest/unittest.py | 2 +- testing/test_nose.py | 44 +++++++++++++++++++++++++++++++++++++++ testing/test_unittest.py | 24 +++++++++++++++++++++ 5 files changed, 74 insertions(+), 5 deletions(-) create mode 100644 changelog/8394.bugfix.rst diff --git a/changelog/8394.bugfix.rst b/changelog/8394.bugfix.rst new file mode 100644 index 00000000000..a0fb5bb71fd --- /dev/null +++ b/changelog/8394.bugfix.rst @@ -0,0 +1 @@ +Use private names for internal fixtures that handle classic setup/teardown so that they don't show up with the default ``--fixtures`` invocation (but they still show up with ``--fixtures -v``). diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 40116ab9c5a..c19d2ed4fb4 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -528,7 +528,7 @@ def _inject_setup_module_fixture(self) -> None: autouse=True, scope="module", # Use a unique name to speed up lookup. - name=f"xunit_setup_module_fixture_{self.obj.__name__}", + name=f"_xunit_setup_module_fixture_{self.obj.__name__}", ) def xunit_setup_module_fixture(request) -> Generator[None, None, None]: if setup_module is not None: @@ -557,7 +557,7 @@ def _inject_setup_function_fixture(self) -> None: autouse=True, scope="function", # Use a unique name to speed up lookup. - name=f"xunit_setup_function_fixture_{self.obj.__name__}", + name=f"_xunit_setup_function_fixture_{self.obj.__name__}", ) def xunit_setup_function_fixture(request) -> Generator[None, None, None]: if request.instance is not None: @@ -809,7 +809,7 @@ def _inject_setup_class_fixture(self) -> None: autouse=True, scope="class", # Use a unique name to speed up lookup. - name=f"xunit_setup_class_fixture_{self.obj.__qualname__}", + name=f"_xunit_setup_class_fixture_{self.obj.__qualname__}", ) def xunit_setup_class_fixture(cls) -> Generator[None, None, None]: if setup_class is not None: @@ -838,7 +838,7 @@ def _inject_setup_method_fixture(self) -> None: autouse=True, scope="function", # Use a unique name to speed up lookup. - name=f"xunit_setup_method_fixture_{self.obj.__qualname__}", + name=f"_xunit_setup_method_fixture_{self.obj.__qualname__}", ) def xunit_setup_method_fixture(self, request) -> Generator[None, None, None]: method = request.function diff --git a/src/_pytest/unittest.py b/src/_pytest/unittest.py index 719eb4e8823..3f88d7a9e2c 100644 --- a/src/_pytest/unittest.py +++ b/src/_pytest/unittest.py @@ -144,7 +144,7 @@ def cleanup(*args): scope=scope, autouse=True, # Use a unique name to speed up lookup. - name=f"unittest_{setup_name}_fixture_{obj.__qualname__}", + name=f"_unittest_{setup_name}_fixture_{obj.__qualname__}", ) def fixture(self, request: FixtureRequest) -> Generator[None, None, None]: if _is_skipped(self): diff --git a/testing/test_nose.py b/testing/test_nose.py index 13429afafd4..77f79b53b3c 100644 --- a/testing/test_nose.py +++ b/testing/test_nose.py @@ -211,6 +211,50 @@ def test_world(): result.stdout.fnmatch_lines(["*2 passed*"]) +def test_fixtures_nose_setup_issue8394(pytester: Pytester) -> None: + pytester.makepyfile( + """ + def setup_module(): + pass + + def teardown_module(): + pass + + def setup_function(func): + pass + + def teardown_function(func): + pass + + def test_world(): + pass + + class Test(object): + def setup_class(cls): + pass + + def teardown_class(cls): + pass + + def setup_method(self, meth): + pass + + def teardown_method(self, meth): + pass + + def test_method(self): pass + """ + ) + match = "*no docstring available*" + result = pytester.runpytest("--fixtures") + assert result.ret == 0 + result.stdout.no_fnmatch_line(match) + + result = pytester.runpytest("--fixtures", "-v") + assert result.ret == 0 + result.stdout.fnmatch_lines([match, match, match, match]) + + def test_nose_setup_ordering(pytester: Pytester) -> None: pytester.makepyfile( """ diff --git a/testing/test_unittest.py b/testing/test_unittest.py index 69bafc26d61..d7f7737153d 100644 --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -302,6 +302,30 @@ def test_teareddown(): reprec.assertoutcome(passed=3) +def test_fixtures_setup_setUpClass_issue8394(pytester: Pytester) -> None: + pytester.makepyfile( + """ + import unittest + class MyTestCase(unittest.TestCase): + @classmethod + def setUpClass(cls): + pass + def test_func1(self): + pass + @classmethod + def tearDownClass(cls): + pass + """ + ) + result = pytester.runpytest("--fixtures") + assert result.ret == 0 + result.stdout.no_fnmatch_line("*no docstring available*") + + result = pytester.runpytest("--fixtures", "-v") + assert result.ret == 0 + result.stdout.fnmatch_lines(["*no docstring available*"]) + + def test_setup_class(pytester: Pytester) -> None: testpath = pytester.makepyfile( """ From 7c792e96c68c0308ca7d6b913dd9fc82cf727012 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 5 Mar 2021 22:22:53 -0300 Subject: [PATCH 0157/2772] Add type annotations to the description instead of signature This configures Sphinx autodoc to include the type annotations along with the description of the function/method, instead of including it into the signature. Fix #8405 --- doc/en/conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/en/conf.py b/doc/en/conf.py index e34ae6856f0..d9c1f3c2d3f 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -35,6 +35,7 @@ # sys.path.insert(0, os.path.abspath('.')) autodoc_member_order = "bysource" +autodoc_typehints = "description" todo_include_todos = 1 # -- General configuration ----------------------------------------------------- From 22dad53a248f50f50b5e000d63a8d3c798868d98 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Sun, 17 Jan 2021 21:20:29 +0100 Subject: [PATCH 0158/2772] implement Node.path as pathlib.Path * reorganize lastfailed node sort Co-authored-by: Bruno Oliveira --- changelog/8251.deprecation.rst | 1 + changelog/8251.feature.rst | 1 + doc/en/deprecations.rst | 10 +++ src/_pytest/cacheprovider.py | 13 ++-- src/_pytest/compat.py | 12 ++++ src/_pytest/deprecated.py | 8 +++ src/_pytest/doctest.py | 21 ++++--- src/_pytest/fixtures.py | 30 ++++++--- src/_pytest/main.py | 11 +++- src/_pytest/nodes.py | 85 ++++++++++++++++++++------ src/_pytest/pytester.py | 5 +- src/_pytest/python.py | 22 ++++--- testing/plugins_integration/pytest.ini | 1 + testing/python/collect.py | 6 +- testing/python/fixtures.py | 4 +- testing/test_collection.py | 32 +++++----- testing/test_mark.py | 3 +- testing/test_runner.py | 4 +- testing/test_terminal.py | 2 +- 19 files changed, 194 insertions(+), 77 deletions(-) create mode 100644 changelog/8251.deprecation.rst create mode 100644 changelog/8251.feature.rst diff --git a/changelog/8251.deprecation.rst b/changelog/8251.deprecation.rst new file mode 100644 index 00000000000..1d988bfc83b --- /dev/null +++ b/changelog/8251.deprecation.rst @@ -0,0 +1 @@ +Deprecate ``Node.fspath`` as we plan to move off `py.path.local `__ and switch to :mod:``pathlib``. diff --git a/changelog/8251.feature.rst b/changelog/8251.feature.rst new file mode 100644 index 00000000000..49aede797a0 --- /dev/null +++ b/changelog/8251.feature.rst @@ -0,0 +1 @@ +Implement ``Node.path`` as a ``pathlib.Path``. diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index a3d7fd49a33..6ecb37b385a 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -19,6 +19,16 @@ Below is a complete list of all pytest features which are considered deprecated. :class:`PytestWarning` or subclasses, which can be filtered using :ref:`standard warning filters `. +``Node.fspath`` in favor of ``pathlib`` and ``Node.path`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 6.3 + +As pytest tries to move off `py.path.local `__ we ported most of the node internals to :mod:`pathlib`. + +Pytest will provide compatibility for quite a while. + + Backward compatibilities in ``Parser.addoption`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py index 585cebf6c9d..03e20bea18c 100755 --- a/src/_pytest/cacheprovider.py +++ b/src/_pytest/cacheprovider.py @@ -218,14 +218,17 @@ def pytest_make_collect_report(self, collector: nodes.Collector): # Sort any lf-paths to the beginning. lf_paths = self.lfplugin._last_failed_paths + res.result = sorted( res.result, - key=lambda x: 0 if Path(str(x.fspath)) in lf_paths else 1, + # use stable sort to priorize last failed + key=lambda x: x.path in lf_paths, + reverse=True, ) return elif isinstance(collector, Module): - if Path(str(collector.fspath)) in self.lfplugin._last_failed_paths: + if collector.path in self.lfplugin._last_failed_paths: out = yield res = out.get_result() result = res.result @@ -246,7 +249,7 @@ def pytest_make_collect_report(self, collector: nodes.Collector): for x in result if x.nodeid in lastfailed # Include any passed arguments (not trivial to filter). - or session.isinitpath(x.fspath) + or session.isinitpath(x.path) # Keep all sub-collectors. or isinstance(x, nodes.Collector) ] @@ -266,7 +269,7 @@ def pytest_make_collect_report( # test-bearing paths and doesn't try to include the paths of their # packages, so don't filter them. if isinstance(collector, Module) and not isinstance(collector, Package): - if Path(str(collector.fspath)) not in self.lfplugin._last_failed_paths: + if collector.path not in self.lfplugin._last_failed_paths: self.lfplugin._skipped_files += 1 return CollectReport( @@ -415,7 +418,7 @@ def pytest_collection_modifyitems( 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) + return sorted(items, key=lambda item: item.path.stat().st_mtime, reverse=True) # type: ignore[no-any-return] def pytest_sessionfinish(self) -> None: config = self.config diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index b354fcb3f63..b9cbf85e04f 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -2,6 +2,7 @@ import enum import functools import inspect +import os import re import sys from contextlib import contextmanager @@ -18,6 +19,7 @@ from typing import Union import attr +import py from _pytest.outcomes import fail from _pytest.outcomes import TEST_OUTCOME @@ -30,6 +32,16 @@ _T = TypeVar("_T") _S = TypeVar("_S") +#: constant to prepare valuing py.path.local replacements/lazy proxies later on +# intended for removal in pytest 8.0 or 9.0 + +LEGACY_PATH = py.path.local + + +def legacy_path(path: Union[str, "os.PathLike[str]"]) -> LEGACY_PATH: + """Internal wrapper to prepare lazy proxies for py.path.local instances""" + return py.path.local(path) + # fmt: off # Singleton type for NOTSET, as described in: diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 5efc004ac94..c203eadc1ad 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -89,6 +89,12 @@ ) +NODE_FSPATH = UnformattedWarning( + PytestDeprecationWarning, + "{type}.fspath is deprecated and will be replaced by {type}.path.\n" + "see TODO;URL for details on replacing py.path.local with pathlib.Path", +) + # You want to make some `__init__` or function "private". # # def my_private_function(some, args): @@ -106,6 +112,8 @@ # # All other calls will get the default _ispytest=False and trigger # the warning (possibly error in the future). + + def check_ispytest(ispytest: bool) -> None: if not ispytest: warn(PRIVATE, stacklevel=3) diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py index 255ca80b913..4942a8f793b 100644 --- a/src/_pytest/doctest.py +++ b/src/_pytest/doctest.py @@ -30,6 +30,7 @@ from _pytest._code.code import ReprFileLocation from _pytest._code.code import TerminalRepr from _pytest._io import TerminalWriter +from _pytest.compat import legacy_path from _pytest.compat import safe_getattr from _pytest.config import Config from _pytest.config.argparsing import Parser @@ -128,10 +129,10 @@ def pytest_collect_file( config = parent.config if fspath.suffix == ".py": if config.option.doctestmodules and not _is_setup_py(fspath): - mod: DoctestModule = DoctestModule.from_parent(parent, fspath=path) + mod: DoctestModule = DoctestModule.from_parent(parent, path=fspath) return mod elif _is_doctest(config, fspath, parent): - txt: DoctestTextfile = DoctestTextfile.from_parent(parent, fspath=path) + txt: DoctestTextfile = DoctestTextfile.from_parent(parent, path=fspath) return txt return None @@ -378,7 +379,7 @@ def repr_failure( # type: ignore[override] def reportinfo(self): assert self.dtest is not None - return self.fspath, self.dtest.lineno, "[doctest] %s" % self.name + return legacy_path(self.path), self.dtest.lineno, "[doctest] %s" % self.name def _get_flag_lookup() -> Dict[str, int]: @@ -425,9 +426,9 @@ def collect(self) -> Iterable[DoctestItem]: # Inspired by doctest.testfile; ideally we would use it directly, # but it doesn't support passing a custom checker. encoding = self.config.getini("doctest_encoding") - text = self.fspath.read_text(encoding) - filename = str(self.fspath) - name = self.fspath.basename + text = self.path.read_text(encoding) + filename = str(self.path) + name = self.path.name globs = {"__name__": "__main__"} optionflags = get_optionflags(self) @@ -534,16 +535,16 @@ def _find( self, tests, obj, name, module, source_lines, globs, seen ) - if self.fspath.basename == "conftest.py": + if self.path.name == "conftest.py": module = self.config.pluginmanager._importconftest( - Path(self.fspath), self.config.getoption("importmode") + self.path, self.config.getoption("importmode") ) else: try: - module = import_path(self.fspath) + module = import_path(self.path) except ImportError: if self.config.getvalue("doctest_ignore_import_errors"): - pytest.skip("unable to import module %r" % self.fspath) + pytest.skip("unable to import module %r" % self.path) else: raise # Uses internal doctest module parsing mechanism. diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 0521d736118..722400ff7aa 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -28,7 +28,6 @@ from typing import Union import attr -import py import _pytest from _pytest import nodes @@ -46,6 +45,8 @@ from _pytest.compat import getimfunc from _pytest.compat import getlocation from _pytest.compat import is_generator +from _pytest.compat import LEGACY_PATH +from _pytest.compat import legacy_path from _pytest.compat import NOTSET from _pytest.compat import safe_getattr from _pytest.config import _PluggyPlugin @@ -53,6 +54,7 @@ from _pytest.config.argparsing import Parser from _pytest.deprecated import check_ispytest from _pytest.deprecated import FILLFUNCARGS +from _pytest.deprecated import NODE_FSPATH from _pytest.deprecated import YIELD_FIXTURE from _pytest.mark import Mark from _pytest.mark import ParameterSet @@ -256,12 +258,12 @@ def get_parametrized_fixture_keys(item: nodes.Item, scopenum: int) -> Iterator[_ if scopenum == 0: # session key: _Key = (argname, param_index) elif scopenum == 1: # package - key = (argname, param_index, item.fspath.dirpath()) + key = (argname, param_index, item.path.parent) elif scopenum == 2: # module - key = (argname, param_index, item.fspath) + key = (argname, param_index, item.path) elif scopenum == 3: # class item_cls = item.cls # type: ignore[attr-defined] - key = (argname, param_index, item.fspath, item_cls) + key = (argname, param_index, item.path, item_cls) yield key @@ -519,12 +521,17 @@ def module(self): return self._pyfuncitem.getparent(_pytest.python.Module).obj @property - def fspath(self) -> py.path.local: - """The file system path of the test module which collected this test.""" + def fspath(self) -> LEGACY_PATH: + """(deprecated) The file system path of the test module which collected this test.""" + warnings.warn(NODE_FSPATH.format(type=type(self).__name__), stacklevel=2) + return legacy_path(self.path) + + @property + def path(self) -> Path: if self.scope not in ("function", "class", "module", "package"): raise AttributeError(f"module not available in {self.scope}-scoped context") # TODO: Remove ignore once _pyfuncitem is properly typed. - return self._pyfuncitem.fspath # type: ignore + return self._pyfuncitem.path # type: ignore @property def keywords(self) -> MutableMapping[str, Any]: @@ -1040,7 +1047,7 @@ def finish(self, request: SubRequest) -> None: if exc: raise exc finally: - hook = self._fixturemanager.session.gethookproxy(request.node.fspath) + hook = self._fixturemanager.session.gethookproxy(request.node.path) hook.pytest_fixture_post_finalizer(fixturedef=self, request=request) # Even if finalization fails, we invalidate the cached fixture # value and remove all finalizers because they may be bound methods @@ -1075,7 +1082,7 @@ def execute(self, request: SubRequest) -> _FixtureValue: self.finish(request) assert self.cached_result is None - hook = self._fixturemanager.session.gethookproxy(request.node.fspath) + hook = self._fixturemanager.session.gethookproxy(request.node.path) result = hook.pytest_fixture_setup(fixturedef=self, request=request) return result @@ -1623,6 +1630,11 @@ def parsefactories( self._holderobjseen.add(holderobj) autousenames = [] for name in dir(holderobj): + # ugly workaround for one of the fspath deprecated property of node + # todo: safely generalize + if isinstance(holderobj, nodes.Node) and name == "fspath": + continue + # The attribute can be an arbitrary descriptor, so the attribute # access below can raise. safe_getatt() ignores such exceptions. obj = safe_getattr(holderobj, name, None) diff --git a/src/_pytest/main.py b/src/_pytest/main.py index 5036601f9bb..3dc00fa691e 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -464,7 +464,12 @@ class Session(nodes.FSCollector): def __init__(self, config: Config) -> None: super().__init__( - config.rootdir, parent=None, config=config, session=self, nodeid="" + path=config.rootpath, + fspath=config.rootdir, + parent=None, + config=config, + session=self, + nodeid="", ) self.testsfailed = 0 self.testscollected = 0 @@ -688,7 +693,7 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: if col: if isinstance(col[0], Package): pkg_roots[str(parent)] = col[0] - node_cache1[Path(col[0].fspath)] = [col[0]] + node_cache1[col[0].path] = [col[0]] # If it's a directory argument, recurse and look for any Subpackages. # Let the Package collector deal with subnodes, don't collect here. @@ -717,7 +722,7 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: continue for x in self._collectfile(path): - key2 = (type(x), Path(x.fspath)) + key2 = (type(x), x.path) if key2 in node_cache2: yield node_cache2[key2] else: diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 2a96d55ad05..47752d34c61 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -23,9 +23,12 @@ from _pytest._code.code import ExceptionInfo from _pytest._code.code import TerminalRepr from _pytest.compat import cached_property +from _pytest.compat import LEGACY_PATH +from _pytest.compat import legacy_path from _pytest.config import Config from _pytest.config import ConftestImportFailure from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH +from _pytest.deprecated import NODE_FSPATH from _pytest.mark.structures import Mark from _pytest.mark.structures import MarkDecorator from _pytest.mark.structures import NodeKeywords @@ -79,6 +82,26 @@ def iterparentnodeids(nodeid: str) -> Iterator[str]: pos = at + len(sep) +def _imply_path( + path: Optional[Path], fspath: Optional[LEGACY_PATH] +) -> Tuple[Path, LEGACY_PATH]: + if path is not None: + if fspath is not None: + if Path(fspath) != path: + raise ValueError( + f"Path({fspath!r}) != {path!r}\n" + "if both path and fspath are given they need to be equal" + ) + assert Path(fspath) == path, f"{fspath} != {path}" + else: + fspath = legacy_path(path) + return path, fspath + + else: + assert fspath is not None + return Path(fspath), fspath + + _NodeType = TypeVar("_NodeType", bound="Node") @@ -110,7 +133,7 @@ class Node(metaclass=NodeMeta): "parent", "config", "session", - "fspath", + "path", "_nodeid", "_store", "__dict__", @@ -123,6 +146,7 @@ def __init__( config: Optional[Config] = None, session: "Optional[Session]" = None, fspath: Optional[py.path.local] = None, + path: Optional[Path] = None, nodeid: Optional[str] = None, ) -> None: #: A unique name within the scope of the parent node. @@ -148,7 +172,7 @@ def __init__( self.session = parent.session #: Filesystem path where this node was collected from (can be None). - self.fspath = fspath or getattr(parent, "fspath", None) + self.path = _imply_path(path or getattr(parent, "path", None), fspath=fspath)[0] # The explicit annotation is to avoid publicly exposing NodeKeywords. #: Keywords/markers collected from all scopes. @@ -174,6 +198,17 @@ def __init__( # own use. Currently only intended for internal plugins. self._store = Store() + @property + def fspath(self): + """(deprecated) returns a py.path.local copy of self.path""" + warnings.warn(NODE_FSPATH.format(type=type(self).__name__), stacklevel=2) + return py.path.local(self.path) + + @fspath.setter + def fspath(self, value: py.path.local): + warnings.warn(NODE_FSPATH.format(type=type(self).__name__), stacklevel=2) + self.path = Path(value) + @classmethod def from_parent(cls, parent: "Node", **kw): """Public constructor for Nodes. @@ -195,7 +230,7 @@ def from_parent(cls, parent: "Node", **kw): @property def ihook(self): """fspath-sensitive hook proxy used to call pytest hooks.""" - return self.session.gethookproxy(self.fspath) + return self.session.gethookproxy(self.path) def __repr__(self) -> str: return "<{} {}>".format(self.__class__.__name__, getattr(self, "name", None)) @@ -476,9 +511,9 @@ def repr_failure( # type: ignore[override] return self._repr_failure_py(excinfo, style=tbstyle) def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None: - if hasattr(self, "fspath"): + if hasattr(self, "path"): traceback = excinfo.traceback - ntraceback = traceback.cut(path=Path(self.fspath)) + ntraceback = traceback.cut(path=self.path) if ntraceback == traceback: ntraceback = ntraceback.cut(excludepath=tracebackcutdir) excinfo.traceback = ntraceback.filter() @@ -497,36 +532,52 @@ def _check_initialpaths_for_relpath( class FSCollector(Collector): def __init__( self, - fspath: py.path.local, + fspath: Optional[py.path.local], + path: Optional[Path], parent=None, config: Optional[Config] = None, session: Optional["Session"] = None, nodeid: Optional[str] = None, ) -> None: + path, fspath = _imply_path(path, fspath=fspath) name = fspath.basename - if parent is not None: - rel = fspath.relto(parent.fspath) - if rel: - name = rel + if parent is not None and parent.path != path: + try: + rel = path.relative_to(parent.path) + except ValueError: + pass + else: + name = str(rel) name = name.replace(os.sep, SEP) - self.fspath = fspath + self.path = Path(fspath) session = session or parent.session if nodeid is None: - nodeid = self.fspath.relto(session.config.rootdir) - - if not nodeid: + try: + nodeid = str(self.path.relative_to(session.config.rootpath)) + except ValueError: nodeid = _check_initialpaths_for_relpath(session, fspath) + if nodeid and os.sep != SEP: nodeid = nodeid.replace(os.sep, SEP) - super().__init__(name, parent, config, session, nodeid=nodeid, fspath=fspath) + super().__init__( + name, parent, config, session, nodeid=nodeid, fspath=fspath, path=path + ) @classmethod - def from_parent(cls, parent, *, fspath, **kw): + def from_parent( + cls, + parent, + *, + fspath: Optional[py.path.local] = None, + path: Optional[Path] = None, + **kw, + ): """The public constructor.""" - return super().from_parent(parent=parent, fspath=fspath, **kw) + path, fspath = _imply_path(path, fspath=fspath) + return super().from_parent(parent=parent, fspath=fspath, path=path, **kw) def gethookproxy(self, fspath: "os.PathLike[str]"): warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 853dfbe9489..f2a6d2aab92 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -61,6 +61,7 @@ from _pytest.outcomes import fail from _pytest.outcomes import importorskip from _pytest.outcomes import skip +from _pytest.pathlib import bestrelpath from _pytest.pathlib import make_numbered_dir from _pytest.reports import CollectReport from _pytest.reports import TestReport @@ -976,10 +977,10 @@ def getpathnode(self, path: Union[str, "os.PathLike[str]"]): :param py.path.local path: Path to the file. """ - path = py.path.local(path) + path = Path(path) config = self.parseconfigure(path) session = Session.from_config(config) - x = session.fspath.bestrelpath(path) + x = bestrelpath(session.path, path) config.hook.pytest_sessionstart(session=session) res = session.perform_collect([x], genitems=False)[0] config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index c19d2ed4fb4..7d518dbbf4b 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -577,7 +577,7 @@ def _importtestmodule(self): # We assume we are only called once per module. importmode = self.config.getoption("--import-mode") try: - mod = import_path(self.fspath, mode=importmode) + mod = import_path(self.path, mode=importmode) except SyntaxError as e: raise self.CollectError( ExceptionInfo.from_current().getrepr(style="short") @@ -603,10 +603,10 @@ def _importtestmodule(self): ) formatted_tb = str(exc_repr) raise self.CollectError( - "ImportError while importing test module '{fspath}'.\n" + "ImportError while importing test module '{path}'.\n" "Hint: make sure your test modules/packages have valid Python names.\n" "Traceback:\n" - "{traceback}".format(fspath=self.fspath, traceback=formatted_tb) + "{traceback}".format(path=self.path, traceback=formatted_tb) ) from e except skip.Exception as e: if e.allow_module_level: @@ -624,18 +624,26 @@ def _importtestmodule(self): class Package(Module): def __init__( self, - fspath: py.path.local, + fspath: Optional[py.path.local], parent: nodes.Collector, # NOTE: following args are unused: config=None, session=None, nodeid=None, + path=Optional[Path], ) -> None: # NOTE: Could be just the following, but kept as-is for compat. # nodes.FSCollector.__init__(self, fspath, parent=parent) + path, fspath = nodes._imply_path(path, fspath=fspath) session = parent.session nodes.FSCollector.__init__( - self, fspath, parent=parent, config=config, session=session, nodeid=nodeid + self, + fspath=fspath, + path=path, + parent=parent, + config=config, + session=session, + nodeid=nodeid, ) self.name = os.path.basename(str(fspath.dirname)) @@ -704,12 +712,12 @@ def _collectfile( return ihook.pytest_collect_file(fspath=fspath, path=path, parent=self) # type: ignore[no-any-return] def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: - this_path = Path(self.fspath).parent + this_path = self.path.parent init_module = this_path / "__init__.py" if init_module.is_file() and path_matches_patterns( init_module, self.config.getini("python_files") ): - yield Module.from_parent(self, fspath=py.path.local(init_module)) + yield Module.from_parent(self, path=init_module) pkg_prefixes: Set[Path] = set() for direntry in visit(str(this_path), recurse=self._recurse): path = Path(direntry.path) diff --git a/testing/plugins_integration/pytest.ini b/testing/plugins_integration/pytest.ini index f6c77b0dee5..b42b07d145a 100644 --- a/testing/plugins_integration/pytest.ini +++ b/testing/plugins_integration/pytest.ini @@ -2,3 +2,4 @@ addopts = --strict-markers filterwarnings = error::pytest.PytestWarning + ignore:.*.fspath is deprecated and will be replaced by .*.path.*:pytest.PytestDeprecationWarning diff --git a/testing/python/collect.py b/testing/python/collect.py index 4256851e254..bb4c937c01e 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -1125,7 +1125,8 @@ def pytest_pycollect_makeitem(collector, name, obj): def test_func_reportinfo(self, pytester: Pytester) -> None: item = pytester.getitem("def test_func(): pass") fspath, lineno, modpath = item.reportinfo() - assert fspath == item.fspath + with pytest.warns(DeprecationWarning): + assert fspath == item.fspath assert lineno == 0 assert modpath == "test_func" @@ -1140,7 +1141,8 @@ def test_hello(self): pass classcol = pytester.collect_by_name(modcol, "TestClass") assert isinstance(classcol, Class) fspath, lineno, msg = classcol.reportinfo() - assert fspath == modcol.fspath + with pytest.warns(DeprecationWarning): + assert fspath == modcol.fspath assert lineno == 1 assert msg == "TestClass" diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index 3d5099c5399..e62143e5c18 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -966,7 +966,9 @@ def test_request_getmodulepath(self, pytester: Pytester) -> None: modcol = pytester.getmodulecol("def test_somefunc(): pass") (item,) = pytester.genitems([modcol]) req = fixtures.FixtureRequest(item, _ispytest=True) - assert req.fspath == modcol.fspath + assert req.path == modcol.path + with pytest.warns(pytest.PytestDeprecationWarning): + assert req.fspath == modcol.fspath def test_request_fixturenames(self, pytester: Pytester) -> None: pytester.makepyfile( diff --git a/testing/test_collection.py b/testing/test_collection.py index 055d27476a6..248071111f3 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -464,13 +464,13 @@ def test_collect_topdir(self, pytester: Pytester) -> None: config = pytester.parseconfig(id) topdir = pytester.path rcol = Session.from_config(config) - assert topdir == rcol.fspath + assert topdir == rcol.path # rootid = rcol.nodeid # root2 = rcol.perform_collect([rcol.nodeid], genitems=False)[0] # assert root2 == rcol, rootid colitems = rcol.perform_collect([rcol.nodeid], genitems=False) assert len(colitems) == 1 - assert colitems[0].fspath == p + assert colitems[0].path == p def get_reported_items(self, hookrec: HookRecorder) -> List[Item]: """Return pytest.Item instances reported by the pytest_collectreport hook""" @@ -494,10 +494,10 @@ def test_collect_protocol_single_function(self, pytester: Pytester) -> None: topdir = pytester.path # noqa hookrec.assert_contains( [ - ("pytest_collectstart", "collector.fspath == topdir"), - ("pytest_make_collect_report", "collector.fspath == topdir"), - ("pytest_collectstart", "collector.fspath == p"), - ("pytest_make_collect_report", "collector.fspath == p"), + ("pytest_collectstart", "collector.path == topdir"), + ("pytest_make_collect_report", "collector.path == topdir"), + ("pytest_collectstart", "collector.path == p"), + ("pytest_make_collect_report", "collector.path == p"), ("pytest_pycollect_makeitem", "name == 'test_func'"), ("pytest_collectreport", "report.result[0].name == 'test_func'"), ] @@ -547,7 +547,7 @@ def pytest_collect_file(path, parent): assert len(items) == 2 hookrec.assert_contains( [ - ("pytest_collectstart", "collector.fspath == collector.session.fspath"), + ("pytest_collectstart", "collector.path == collector.session.path"), ( "pytest_collectstart", "collector.__class__.__name__ == 'SpecialFile'", @@ -570,7 +570,7 @@ def test_collect_subdir_event_ordering(self, pytester: Pytester) -> None: pprint.pprint(hookrec.calls) hookrec.assert_contains( [ - ("pytest_collectstart", "collector.fspath == test_aaa"), + ("pytest_collectstart", "collector.path == test_aaa"), ("pytest_pycollect_makeitem", "name == 'test_func'"), ("pytest_collectreport", "report.nodeid.startswith('aaa/test_aaa.py')"), ] @@ -592,10 +592,10 @@ def test_collect_two_commandline_args(self, pytester: Pytester) -> None: pprint.pprint(hookrec.calls) hookrec.assert_contains( [ - ("pytest_collectstart", "collector.fspath == test_aaa"), + ("pytest_collectstart", "collector.path == test_aaa"), ("pytest_pycollect_makeitem", "name == 'test_func'"), ("pytest_collectreport", "report.nodeid == 'aaa/test_aaa.py'"), - ("pytest_collectstart", "collector.fspath == test_bbb"), + ("pytest_collectstart", "collector.path == test_bbb"), ("pytest_pycollect_makeitem", "name == 'test_func'"), ("pytest_collectreport", "report.nodeid == 'bbb/test_bbb.py'"), ] @@ -609,7 +609,9 @@ def test_serialization_byid(self, pytester: Pytester) -> None: items2, hookrec = pytester.inline_genitems(item.nodeid) (item2,) = items2 assert item2.name == item.name - assert item2.fspath == item.fspath + with pytest.warns(DeprecationWarning): + assert item2.fspath == item.fspath + assert item2.path == item.path def test_find_byid_without_instance_parents(self, pytester: Pytester) -> None: p = pytester.makepyfile( @@ -1347,14 +1349,10 @@ def test_fscollector_from_parent(pytester: Pytester, request: FixtureRequest) -> """ class MyCollector(pytest.File): - def __init__(self, fspath, parent, x): - super().__init__(fspath, parent) + def __init__(self, *k, x, **kw): + super().__init__(*k, **kw) self.x = x - @classmethod - def from_parent(cls, parent, *, fspath, x): - return super().from_parent(parent=parent, fspath=fspath, x=x) - collector = MyCollector.from_parent( parent=request.session, fspath=py.path.local(pytester.path) / "foo", x=10 ) diff --git a/testing/test_mark.py b/testing/test_mark.py index 420faf91ec9..77991f9e273 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -1048,11 +1048,12 @@ class TestBarClass(BaseTests): # assert skipped_k == failed_k == 0 -def test_addmarker_order() -> None: +def test_addmarker_order(pytester) -> None: session = mock.Mock() session.own_markers = [] session.parent = None session.nodeid = "" + session.path = pytester.path node = Node.from_parent(session, name="Test") node.add_marker("foo") node.add_marker("bar") diff --git a/testing/test_runner.py b/testing/test_runner.py index abb87c6d3d4..a34cd98f964 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -447,9 +447,9 @@ class TestClass(object): assert not rep.skipped assert rep.passed locinfo = rep.location - assert locinfo[0] == col.fspath.basename + assert locinfo[0] == col.path.name assert not locinfo[1] - assert locinfo[2] == col.fspath.basename + assert locinfo[2] == col.path.name res = rep.result assert len(res) == 2 assert res[0].name == "test_func1" diff --git a/testing/test_terminal.py b/testing/test_terminal.py index e536f70989c..53bced8e68e 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -133,7 +133,7 @@ def test_show_runtest_logstart(self, pytester: Pytester, linecomp) -> None: item.config.pluginmanager.register(tr) location = item.reportinfo() tr.config.hook.pytest_runtest_logstart( - nodeid=item.nodeid, location=location, fspath=str(item.fspath) + nodeid=item.nodeid, location=location, fspath=str(item.path) ) linecomp.assert_contains_lines(["*test_show_runtest_logstart.py*"]) From 77cb110258c09d29eff90078722115ca8a0a1619 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Mon, 22 Feb 2021 09:43:52 +0100 Subject: [PATCH 0159/2772] drop usage of py.path.local calls Co-authored-by: Bruno Oliveira --- .pre-commit-config.yaml | 6 ++++ src/_pytest/_code/code.py | 3 +- src/_pytest/cacheprovider.py | 7 ++-- src/_pytest/compat.py | 11 +++--- src/_pytest/config/__init__.py | 25 ++++++------- src/_pytest/deprecated.py | 2 +- src/_pytest/doctest.py | 5 ++- src/_pytest/hookspec.py | 22 ++++++------ src/_pytest/main.py | 6 ++-- src/_pytest/nodes.py | 28 +++++++-------- src/_pytest/pytester.py | 65 +++++++++++++++++----------------- src/_pytest/python.py | 20 +++++------ src/_pytest/reports.py | 4 +-- src/_pytest/tmpdir.py | 23 ++++++------ testing/test_collection.py | 10 +++--- testing/test_main.py | 2 +- testing/test_nodes.py | 9 +++-- testing/test_parseopt.py | 9 +++-- testing/test_pathlib.py | 7 ++-- testing/test_reports.py | 5 ++- 20 files changed, 138 insertions(+), 131 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b324b1f484e..24196151952 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -87,3 +87,9 @@ repos: xml\. ) types: [python] + - id: py-path-deprecated + name: py.path usage is deprecated + language: pygrep + entry: \bpy\.path\.local + exclude: docs + types: [python] diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index b8521756067..331aaabc780 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -31,7 +31,6 @@ import attr import pluggy -import py import _pytest from _pytest._code.source import findsource @@ -1230,7 +1229,7 @@ def getfslineno(obj: object) -> Tuple[Union[str, Path], int]: if _PLUGGY_DIR.name == "__init__.py": _PLUGGY_DIR = _PLUGGY_DIR.parent _PYTEST_DIR = Path(_pytest.__file__).parent -_PY_DIR = Path(py.__file__).parent +_PY_DIR = Path(__import__("py").__file__).parent def filter_traceback(entry: TracebackEntry) -> bool: diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py index 03e20bea18c..a7ec7989184 100755 --- a/src/_pytest/cacheprovider.py +++ b/src/_pytest/cacheprovider.py @@ -13,7 +13,6 @@ from typing import Union import attr -import py from .pathlib import resolve_from_str from .pathlib import rm_rf @@ -21,6 +20,8 @@ from _pytest import nodes from _pytest._io import TerminalWriter from _pytest.compat import final +from _pytest.compat import LEGACY_PATH +from _pytest.compat import legacy_path from _pytest.config import Config from _pytest.config import ExitCode from _pytest.config import hookimpl @@ -120,7 +121,7 @@ def warn(self, fmt: str, *, _ispytest: bool = False, **args: object) -> None: stacklevel=3, ) - def makedir(self, name: str) -> py.path.local: + def makedir(self, name: str) -> LEGACY_PATH: """Return a directory path object with the given name. If the directory does not yet exist, it will be created. You can use @@ -137,7 +138,7 @@ def makedir(self, name: str) -> py.path.local: 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) + return legacy_path(res) def _getvaluepath(self, key: str) -> Path: return self._cachedir.joinpath(self._CACHE_PREFIX_VALUES, Path(key)) diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index b9cbf85e04f..4236618e8b4 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -32,15 +32,18 @@ _T = TypeVar("_T") _S = TypeVar("_S") -#: constant to prepare valuing py.path.local replacements/lazy proxies later on +#: constant to prepare valuing pylib path replacements/lazy proxies later on # intended for removal in pytest 8.0 or 9.0 -LEGACY_PATH = py.path.local +# fmt: off +# intentional space to create a fake difference for the verification +LEGACY_PATH = py.path. local +# fmt: on def legacy_path(path: Union[str, "os.PathLike[str]"]) -> LEGACY_PATH: - """Internal wrapper to prepare lazy proxies for py.path.local instances""" - return py.path.local(path) + """Internal wrapper to prepare lazy proxies for legacy_path instances""" + return LEGACY_PATH(path) # fmt: off diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index c029c29a3a2..3f138efa750 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -32,7 +32,6 @@ from typing import Union import attr -import py from pluggy import HookimplMarker from pluggy import HookspecMarker from pluggy import PluginManager @@ -48,6 +47,8 @@ from _pytest._io import TerminalWriter from _pytest.compat import final from _pytest.compat import importlib_metadata +from _pytest.compat import LEGACY_PATH +from _pytest.compat import legacy_path from _pytest.outcomes import fail from _pytest.outcomes import Skipped from _pytest.pathlib import absolutepath @@ -937,15 +938,15 @@ def __init__( self.cache: Optional[Cache] = None @property - def invocation_dir(self) -> py.path.local: + def invocation_dir(self) -> LEGACY_PATH: """The directory from which pytest was invoked. Prefer to use :attr:`invocation_params.dir `, which is a :class:`pathlib.Path`. - :type: py.path.local + :type: LEGACY_PATH """ - return py.path.local(str(self.invocation_params.dir)) + return legacy_path(str(self.invocation_params.dir)) @property def rootpath(self) -> Path: @@ -958,14 +959,14 @@ def rootpath(self) -> Path: return self._rootpath @property - def rootdir(self) -> py.path.local: + def rootdir(self) -> LEGACY_PATH: """The path to the :ref:`rootdir `. Prefer to use :attr:`rootpath`, which is a :class:`pathlib.Path`. - :type: py.path.local + :type: LEGACY_PATH """ - return py.path.local(str(self.rootpath)) + return legacy_path(str(self.rootpath)) @property def inipath(self) -> Optional[Path]: @@ -978,14 +979,14 @@ def inipath(self) -> Optional[Path]: return self._inipath @property - def inifile(self) -> Optional[py.path.local]: + def inifile(self) -> Optional[LEGACY_PATH]: """The path to the :ref:`configfile `. Prefer to use :attr:`inipath`, which is a :class:`pathlib.Path`. - :type: Optional[py.path.local] + :type: Optional[LEGACY_PATH] """ - return py.path.local(str(self.inipath)) if self.inipath else None + return legacy_path(str(self.inipath)) if self.inipath else None def add_cleanup(self, func: Callable[[], None]) -> None: """Add a function to be called when the config object gets out of @@ -1420,7 +1421,7 @@ def _getini(self, name: str): assert self.inipath is not None dp = self.inipath.parent input_values = shlex.split(value) if isinstance(value, str) else value - return [py.path.local(str(dp / x)) for x in input_values] + return [legacy_path(str(dp / x)) for x in input_values] elif type == "args": return shlex.split(value) if isinstance(value, str) else value elif type == "linelist": @@ -1446,7 +1447,7 @@ def _getconftest_pathlist(self, name: str, path: Path) -> Optional[List[Path]]: for relroot in relroots: if isinstance(relroot, Path): pass - elif isinstance(relroot, py.path.local): + elif isinstance(relroot, LEGACY_PATH): relroot = Path(relroot) else: relroot = relroot.replace("/", os.sep) diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index c203eadc1ad..596574877bf 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -92,7 +92,7 @@ NODE_FSPATH = UnformattedWarning( PytestDeprecationWarning, "{type}.fspath is deprecated and will be replaced by {type}.path.\n" - "see TODO;URL for details on replacing py.path.local with pathlib.Path", + "see https://docs.pytest.org/en/latest/deprecations.html#node-fspath-in-favor-of-pathlib-and-node-path", ) # You want to make some `__init__` or function "private". diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py index 4942a8f793b..41d295daaba 100644 --- a/src/_pytest/doctest.py +++ b/src/_pytest/doctest.py @@ -22,14 +22,13 @@ from typing import TYPE_CHECKING from typing import Union -import py.path - import pytest from _pytest import outcomes from _pytest._code.code import ExceptionInfo from _pytest._code.code import ReprFileLocation from _pytest._code.code import TerminalRepr from _pytest._io import TerminalWriter +from _pytest.compat import LEGACY_PATH from _pytest.compat import legacy_path from _pytest.compat import safe_getattr from _pytest.config import Config @@ -123,7 +122,7 @@ def pytest_unconfigure() -> None: def pytest_collect_file( fspath: Path, - path: py.path.local, + path: LEGACY_PATH, parent: Collector, ) -> Optional[Union["DoctestModule", "DoctestTextfile"]]: config = parent.config diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index b0b8fd53d85..7d5f767db7e 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -11,7 +11,6 @@ from typing import TYPE_CHECKING from typing import Union -import py.path from pluggy import HookspecMarker from _pytest.deprecated import WARNING_CAPTURED_HOOK @@ -42,6 +41,7 @@ from _pytest.reports import TestReport from _pytest.runner import CallInfo from _pytest.terminal import TerminalReporter + from _pytest.compat import LEGACY_PATH hookspec = HookspecMarker("pytest") @@ -263,7 +263,7 @@ def pytest_collection_finish(session: "Session") -> None: @hookspec(firstresult=True) def pytest_ignore_collect( - fspath: Path, path: py.path.local, config: "Config" + fspath: Path, path: "LEGACY_PATH", config: "Config" ) -> Optional[bool]: """Return True to prevent considering this path for collection. @@ -273,7 +273,7 @@ def pytest_ignore_collect( Stops at first non-None result, see :ref:`firstresult`. :param pathlib.Path fspath: The path to analyze. - :param py.path.local path: The path to analyze. + :param LEGACY_PATH path: The path to analyze. :param _pytest.config.Config config: The pytest config object. .. versionchanged:: 6.3.0 @@ -283,14 +283,14 @@ def pytest_ignore_collect( def pytest_collect_file( - fspath: Path, path: py.path.local, parent: "Collector" + fspath: Path, path: "LEGACY_PATH", parent: "Collector" ) -> "Optional[Collector]": """Create a Collector for the given path, or None if not relevant. The new node needs to have the specified ``parent`` as a parent. :param pathlib.Path fspath: The path to analyze. - :param py.path.local path: The path to collect. + :param LEGACY_PATH path: The path to collect. .. versionchanged:: 6.3.0 The ``fspath`` parameter was added as a :class:`pathlib.Path` @@ -335,7 +335,7 @@ def pytest_make_collect_report(collector: "Collector") -> "Optional[CollectRepor @hookspec(firstresult=True) def pytest_pycollect_makemodule( - fspath: Path, path: py.path.local, parent + fspath: Path, path: "LEGACY_PATH", parent ) -> Optional["Module"]: """Return a Module collector or None for the given path. @@ -346,7 +346,7 @@ def pytest_pycollect_makemodule( Stops at first non-None result, see :ref:`firstresult`. :param pathlib.Path fspath: The path of the module to collect. - :param py.path.local path: The path of the module to collect. + :param legacy_path path: The path of the module to collect. .. versionchanged:: 6.3.0 The ``fspath`` parameter was added as a :class:`pathlib.Path` @@ -676,13 +676,13 @@ def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> No def pytest_report_header( - config: "Config", startpath: Path, startdir: py.path.local + config: "Config", startpath: Path, startdir: "LEGACY_PATH" ) -> Union[str, List[str]]: """Return a string or list of strings to be displayed as header info for terminal reporting. :param _pytest.config.Config config: The pytest config object. :param Path startpath: The starting dir. - :param py.path.local startdir: The starting dir. + :param LEGACY_PATH startdir: The starting dir. .. note:: @@ -706,7 +706,7 @@ def pytest_report_header( def pytest_report_collectionfinish( config: "Config", startpath: Path, - startdir: py.path.local, + startdir: "LEGACY_PATH", items: Sequence["Item"], ) -> Union[str, List[str]]: """Return a string or list of strings to be displayed after collection @@ -718,7 +718,7 @@ def pytest_report_collectionfinish( :param _pytest.config.Config config: The pytest config object. :param Path startpath: The starting path. - :param py.path.local startdir: The starting dir. + :param LEGACY_PATH startdir: The starting dir. :param items: List of pytest items that are going to be executed; this list should not be modified. .. note:: diff --git a/src/_pytest/main.py b/src/_pytest/main.py index 3dc00fa691e..3e7213489ff 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -21,11 +21,11 @@ from typing import Union import attr -import py import _pytest._code from _pytest import nodes from _pytest.compat import final +from _pytest.compat import legacy_path from _pytest.config import Config from _pytest.config import directory_arg from _pytest.config import ExitCode @@ -543,7 +543,7 @@ def _recurse(self, direntry: "os.DirEntry[str]") -> bool: if direntry.name == "__pycache__": return False fspath = Path(direntry.path) - path = py.path.local(fspath) + path = legacy_path(fspath) ihook = self.gethookproxy(fspath.parent) if ihook.pytest_ignore_collect(fspath=fspath, path=path, config=self.config): return False @@ -555,7 +555,7 @@ def _recurse(self, direntry: "os.DirEntry[str]") -> bool: def _collectfile( self, fspath: Path, handle_dupes: bool = True ) -> Sequence[nodes.Collector]: - path = py.path.local(fspath) + path = legacy_path(fspath) assert ( fspath.is_file() ), "{!r} is not a file (isdir={!r}, exists={!r}, islink={!r})".format( diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 47752d34c61..9d93659e1ad 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -16,8 +16,6 @@ from typing import TypeVar from typing import Union -import py - import _pytest._code from _pytest._code import getfslineno from _pytest._code.code import ExceptionInfo @@ -145,7 +143,7 @@ def __init__( parent: "Optional[Node]" = None, config: Optional[Config] = None, session: "Optional[Session]" = None, - fspath: Optional[py.path.local] = None, + fspath: Optional[LEGACY_PATH] = None, path: Optional[Path] = None, nodeid: Optional[str] = None, ) -> None: @@ -199,13 +197,13 @@ def __init__( self._store = Store() @property - def fspath(self): - """(deprecated) returns a py.path.local copy of self.path""" + def fspath(self) -> LEGACY_PATH: + """(deprecated) returns a legacy_path copy of self.path""" warnings.warn(NODE_FSPATH.format(type=type(self).__name__), stacklevel=2) - return py.path.local(self.path) + return legacy_path(self.path) @fspath.setter - def fspath(self, value: py.path.local): + def fspath(self, value: LEGACY_PATH) -> None: warnings.warn(NODE_FSPATH.format(type=type(self).__name__), stacklevel=2) self.path = Path(value) @@ -464,7 +462,7 @@ def get_fslocation_from_item(node: "Node") -> Tuple[Union[str, Path], Optional[i * "obj": a Python object that the node wraps. * "fspath": just a path - :rtype: A tuple of (str|py.path.local, int) with filename and line number. + :rtype: A tuple of (str|Path, int) with filename and line number. """ # See Item.location. location: Optional[Tuple[str, Optional[int], str]] = getattr(node, "location", None) @@ -520,10 +518,10 @@ def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None: def _check_initialpaths_for_relpath( - session: "Session", fspath: py.path.local + session: "Session", fspath: LEGACY_PATH ) -> Optional[str]: for initial_path in session._initialpaths: - initial_path_ = py.path.local(initial_path) + initial_path_ = legacy_path(initial_path) if fspath.common(initial_path_) == initial_path_: return fspath.relto(initial_path_) return None @@ -532,7 +530,7 @@ def _check_initialpaths_for_relpath( class FSCollector(Collector): def __init__( self, - fspath: Optional[py.path.local], + fspath: Optional[LEGACY_PATH], path: Optional[Path], parent=None, config: Optional[Config] = None, @@ -571,7 +569,7 @@ def from_parent( cls, parent, *, - fspath: Optional[py.path.local] = None, + fspath: Optional[LEGACY_PATH] = None, path: Optional[Path] = None, **kw, ): @@ -638,8 +636,10 @@ def add_report_section(self, when: str, key: str, content: str) -> None: if content: self._report_sections.append((when, key, content)) - def reportinfo(self) -> Tuple[Union[py.path.local, str], Optional[int], str]: - return self.fspath, None, "" + def reportinfo(self) -> Tuple[Union[LEGACY_PATH, str], Optional[int], str]: + + # TODO: enable Path objects in reportinfo + return legacy_path(self.path), None, "" @cached_property def location(self) -> Tuple[str, Optional[int], str]: diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index f2a6d2aab92..699738e1282 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -34,7 +34,6 @@ from weakref import WeakKeyDictionary import attr -import py from iniconfig import IniConfig from iniconfig import SectionWrapper @@ -42,6 +41,8 @@ from _pytest._code import Source from _pytest.capture import _get_multicapture from _pytest.compat import final +from _pytest.compat import LEGACY_PATH +from _pytest.compat import legacy_path from _pytest.compat import NOTSET from _pytest.compat import NotSetType from _pytest.config import _PluggyPlugin @@ -475,7 +476,7 @@ def pytester(request: FixtureRequest, tmp_path_factory: TempPathFactory) -> "Pyt def testdir(pytester: "Pytester") -> "Testdir": """ Identical to :fixture:`pytester`, and provides an instance whose methods return - legacy ``py.path.local`` objects instead when applicable. + legacy ``LEGACY_PATH`` objects instead when applicable. New code should avoid using :fixture:`testdir` in favor of :fixture:`pytester`. """ @@ -934,10 +935,10 @@ def copy_example(self, name: Optional[str] = None) -> Path: example_path = example_dir.joinpath(name) if example_path.is_dir() and not example_path.joinpath("__init__.py").is_file(): - # TODO: py.path.local.copy can copy files to existing directories, + # TODO: legacy_path.copy can copy files to existing directories, # while with shutil.copytree the destination directory cannot exist, - # we will need to roll our own in order to drop py.path.local completely - py.path.local(example_path).copy(py.path.local(self.path)) + # we will need to roll our own in order to drop legacy_path completely + legacy_path(example_path).copy(legacy_path(self.path)) return self.path elif example_path.is_file(): result = self.path.joinpath(example_path.name) @@ -958,12 +959,12 @@ def getnode( :param _pytest.config.Config config: A pytest config. See :py:meth:`parseconfig` and :py:meth:`parseconfigure` for creating it. - :param py.path.local arg: + :param os.PathLike[str] arg: Path to the file. """ session = Session.from_config(config) assert "::" not in str(arg) - p = py.path.local(arg) + p = legacy_path(arg) config.hook.pytest_sessionstart(session=session) res = session.perform_collect([str(p)], genitems=False)[0] config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK) @@ -975,7 +976,7 @@ def getpathnode(self, path: Union[str, "os.PathLike[str]"]): This is like :py:meth:`getnode` but uses :py:meth:`parseconfigure` to create the (configured) pytest Config instance. - :param py.path.local path: Path to the file. + :param os.PathLike[str] path: Path to the file. """ path = Path(path) config = self.parseconfigure(path) @@ -1520,10 +1521,10 @@ def assert_contains_lines(self, lines2: Sequence[str]) -> None: @attr.s(repr=False, str=False, init=False) class Testdir: """ - Similar to :class:`Pytester`, but this class works with legacy py.path.local objects instead. + Similar to :class:`Pytester`, but this class works with legacy legacy_path objects instead. All methods just forward to an internal :class:`Pytester` instance, converting results - to `py.path.local` objects as necessary. + to `legacy_path` objects as necessary. """ __test__ = False @@ -1537,13 +1538,13 @@ def __init__(self, pytester: Pytester, *, _ispytest: bool = False) -> None: self._pytester = pytester @property - def tmpdir(self) -> py.path.local: + def tmpdir(self) -> LEGACY_PATH: """Temporary directory where tests are executed.""" - return py.path.local(self._pytester.path) + return legacy_path(self._pytester.path) @property - def test_tmproot(self) -> py.path.local: - return py.path.local(self._pytester._test_tmproot) + def test_tmproot(self) -> LEGACY_PATH: + return legacy_path(self._pytester._test_tmproot) @property def request(self): @@ -1573,7 +1574,7 @@ def finalize(self) -> None: """See :meth:`Pytester._finalize`.""" return self._pytester._finalize() - def makefile(self, ext, *args, **kwargs) -> py.path.local: + def makefile(self, ext, *args, **kwargs) -> LEGACY_PATH: """See :meth:`Pytester.makefile`.""" if ext and not ext.startswith("."): # pytester.makefile is going to throw a ValueError in a way that @@ -1583,47 +1584,47 @@ def makefile(self, ext, *args, **kwargs) -> py.path.local: # allowed this, we will prepend "." as a workaround to avoid breaking # testdir usage that worked before ext = "." + ext - return py.path.local(str(self._pytester.makefile(ext, *args, **kwargs))) + return legacy_path(self._pytester.makefile(ext, *args, **kwargs)) - def makeconftest(self, source) -> py.path.local: + def makeconftest(self, source) -> LEGACY_PATH: """See :meth:`Pytester.makeconftest`.""" - return py.path.local(str(self._pytester.makeconftest(source))) + return legacy_path(self._pytester.makeconftest(source)) - def makeini(self, source) -> py.path.local: + def makeini(self, source) -> LEGACY_PATH: """See :meth:`Pytester.makeini`.""" - return py.path.local(str(self._pytester.makeini(source))) + return legacy_path(self._pytester.makeini(source)) def getinicfg(self, source: str) -> SectionWrapper: """See :meth:`Pytester.getinicfg`.""" return self._pytester.getinicfg(source) - def makepyprojecttoml(self, source) -> py.path.local: + def makepyprojecttoml(self, source) -> LEGACY_PATH: """See :meth:`Pytester.makepyprojecttoml`.""" - return py.path.local(str(self._pytester.makepyprojecttoml(source))) + return legacy_path(self._pytester.makepyprojecttoml(source)) - def makepyfile(self, *args, **kwargs) -> py.path.local: + def makepyfile(self, *args, **kwargs) -> LEGACY_PATH: """See :meth:`Pytester.makepyfile`.""" - return py.path.local(str(self._pytester.makepyfile(*args, **kwargs))) + return legacy_path(self._pytester.makepyfile(*args, **kwargs)) - def maketxtfile(self, *args, **kwargs) -> py.path.local: + def maketxtfile(self, *args, **kwargs) -> LEGACY_PATH: """See :meth:`Pytester.maketxtfile`.""" - return py.path.local(str(self._pytester.maketxtfile(*args, **kwargs))) + return legacy_path(self._pytester.maketxtfile(*args, **kwargs)) def syspathinsert(self, path=None) -> None: """See :meth:`Pytester.syspathinsert`.""" return self._pytester.syspathinsert(path) - def mkdir(self, name) -> py.path.local: + def mkdir(self, name) -> LEGACY_PATH: """See :meth:`Pytester.mkdir`.""" - return py.path.local(str(self._pytester.mkdir(name))) + return legacy_path(self._pytester.mkdir(name)) - def mkpydir(self, name) -> py.path.local: + def mkpydir(self, name) -> LEGACY_PATH: """See :meth:`Pytester.mkpydir`.""" - return py.path.local(str(self._pytester.mkpydir(name))) + return legacy_path(self._pytester.mkpydir(name)) - def copy_example(self, name=None) -> py.path.local: + def copy_example(self, name=None) -> LEGACY_PATH: """See :meth:`Pytester.copy_example`.""" - return py.path.local(str(self._pytester.copy_example(name))) + return legacy_path(self._pytester.copy_example(name)) def getnode(self, config: Config, arg) -> Optional[Union[Item, Collector]]: """See :meth:`Pytester.getnode`.""" diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 7d518dbbf4b..ccd685f54a9 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -26,8 +26,6 @@ from typing import TYPE_CHECKING from typing import Union -import py - import _pytest from _pytest import fixtures from _pytest import nodes @@ -45,6 +43,8 @@ from _pytest.compat import getlocation from _pytest.compat import is_async_function from _pytest.compat import is_generator +from _pytest.compat import LEGACY_PATH +from _pytest.compat import legacy_path from _pytest.compat import NOTSET from _pytest.compat import REGEX_TYPE from _pytest.compat import safe_getattr @@ -189,7 +189,7 @@ def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]: def pytest_collect_file( - fspath: Path, path: py.path.local, parent: nodes.Collector + fspath: Path, path: LEGACY_PATH, parent: nodes.Collector ) -> Optional["Module"]: if fspath.suffix == ".py": if not parent.session.isinitpath(fspath): @@ -210,7 +210,7 @@ def path_matches_patterns(path: Path, patterns: Iterable[str]) -> bool: return any(fnmatch_ex(pattern, path) for pattern in patterns) -def pytest_pycollect_makemodule(fspath: Path, path: py.path.local, parent) -> "Module": +def pytest_pycollect_makemodule(fspath: Path, path: LEGACY_PATH, parent) -> "Module": if fspath.name == "__init__.py": pkg: Package = Package.from_parent(parent, fspath=path) return pkg @@ -321,7 +321,7 @@ def getmodpath(self, stopatmodule: bool = True, includemodule: bool = False) -> parts.reverse() return ".".join(parts) - def reportinfo(self) -> Tuple[Union[py.path.local, str], int, str]: + def reportinfo(self) -> Tuple[Union[LEGACY_PATH, str], int, str]: # XXX caching? obj = self.obj compat_co_firstlineno = getattr(obj, "compat_co_firstlineno", None) @@ -330,12 +330,12 @@ def reportinfo(self) -> Tuple[Union[py.path.local, str], int, str]: file_path = sys.modules[obj.__module__].__file__ if file_path.endswith(".pyc"): file_path = file_path[:-1] - fspath: Union[py.path.local, str] = file_path + fspath: Union[LEGACY_PATH, str] = file_path lineno = compat_co_firstlineno else: path, lineno = getfslineno(obj) if isinstance(path, Path): - fspath = py.path.local(path) + fspath = legacy_path(path) else: fspath = path modpath = self.getmodpath() @@ -624,7 +624,7 @@ def _importtestmodule(self): class Package(Module): def __init__( self, - fspath: Optional[py.path.local], + fspath: Optional[LEGACY_PATH], parent: nodes.Collector, # NOTE: following args are unused: config=None, @@ -675,7 +675,7 @@ def _recurse(self, direntry: "os.DirEntry[str]") -> bool: if direntry.name == "__pycache__": return False fspath = Path(direntry.path) - path = py.path.local(fspath) + path = legacy_path(fspath) ihook = self.session.gethookproxy(fspath.parent) if ihook.pytest_ignore_collect(fspath=fspath, path=path, config=self.config): return False @@ -687,7 +687,7 @@ def _recurse(self, direntry: "os.DirEntry[str]") -> bool: def _collectfile( self, fspath: Path, handle_dupes: bool = True ) -> Sequence[nodes.Collector]: - path = py.path.local(fspath) + path = legacy_path(fspath) assert ( fspath.is_file() ), "{!r} is not a file (isdir={!r}, exists={!r}, islink={!r})".format( diff --git a/src/_pytest/reports.py b/src/_pytest/reports.py index 303f731ddaa..657e0683378 100644 --- a/src/_pytest/reports.py +++ b/src/_pytest/reports.py @@ -15,7 +15,6 @@ from typing import Union import attr -import py from _pytest._code.code import ExceptionChainRepr from _pytest._code.code import ExceptionInfo @@ -30,6 +29,7 @@ from _pytest._code.code import TerminalRepr from _pytest._io import TerminalWriter from _pytest.compat import final +from _pytest.compat import LEGACY_PATH from _pytest.config import Config from _pytest.nodes import Collector from _pytest.nodes import Item @@ -500,7 +500,7 @@ def serialize_exception_longrepr(rep: BaseReport) -> Dict[str, Any]: else: d["longrepr"] = report.longrepr for name in d: - if isinstance(d[name], (py.path.local, Path)): + if isinstance(d[name], (LEGACY_PATH, Path)): d[name] = str(d[name]) elif name == "result": d[name] = None # for now diff --git a/src/_pytest/tmpdir.py b/src/_pytest/tmpdir.py index 47729ae5fee..d30e1e57feb 100644 --- a/src/_pytest/tmpdir.py +++ b/src/_pytest/tmpdir.py @@ -6,13 +6,14 @@ from typing import Optional import attr -import py from .pathlib import ensure_reset_dir from .pathlib import LOCK_TIMEOUT from .pathlib import make_numbered_dir from .pathlib import make_numbered_dir_with_cleanup from _pytest.compat import final +from _pytest.compat import LEGACY_PATH +from _pytest.compat import legacy_path from _pytest.config import Config from _pytest.deprecated import check_ispytest from _pytest.fixtures import fixture @@ -133,7 +134,7 @@ def getbasetemp(self) -> Path: @final @attr.s(init=False) class TempdirFactory: - """Backward comptibility wrapper that implements :class:``py.path.local`` + """Backward comptibility wrapper that implements :class:``_pytest.compat.LEGACY_PATH`` for :class:``TempPathFactory``.""" _tmppath_factory = attr.ib(type=TempPathFactory) @@ -144,13 +145,13 @@ def __init__( check_ispytest(_ispytest) self._tmppath_factory = tmppath_factory - def mktemp(self, basename: str, numbered: bool = True) -> py.path.local: - """Same as :meth:`TempPathFactory.mktemp`, but returns a ``py.path.local`` object.""" - return py.path.local(self._tmppath_factory.mktemp(basename, numbered).resolve()) + def mktemp(self, basename: str, numbered: bool = True) -> LEGACY_PATH: + """Same as :meth:`TempPathFactory.mktemp`, but returns a ``_pytest.compat.LEGACY_PATH`` object.""" + return legacy_path(self._tmppath_factory.mktemp(basename, numbered).resolve()) - def getbasetemp(self) -> py.path.local: + def getbasetemp(self) -> LEGACY_PATH: """Backward compat wrapper for ``_tmppath_factory.getbasetemp``.""" - return py.path.local(self._tmppath_factory.getbasetemp().resolve()) + return legacy_path(self._tmppath_factory.getbasetemp().resolve()) def get_user() -> Optional[str]: @@ -202,7 +203,7 @@ def _mk_tmp(request: FixtureRequest, factory: TempPathFactory) -> Path: @fixture -def tmpdir(tmp_path: Path) -> py.path.local: +def tmpdir(tmp_path: Path) -> LEGACY_PATH: """Return a temporary directory path object which is unique to each test function invocation, created as a sub directory of the base temporary directory. @@ -212,11 +213,11 @@ def tmpdir(tmp_path: Path) -> py.path.local: ``--basetemp`` is used then it is cleared each session. See :ref:`base temporary directory`. - The returned object is a `py.path.local`_ path object. + The returned object is a `legacy_path`_ object. - .. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html + .. _legacy_path: https://py.readthedocs.io/en/latest/path.html """ - return py.path.local(tmp_path) + return legacy_path(tmp_path) @fixture diff --git a/testing/test_collection.py b/testing/test_collection.py index 248071111f3..5f0b5902ab8 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -6,8 +6,6 @@ from pathlib import Path from typing import List -import py.path - import pytest from _pytest.config import ExitCode from _pytest.fixtures import FixtureRequest @@ -369,9 +367,10 @@ def pytest_ignore_collect(path, config): def test_collectignore_exclude_on_option(self, pytester: Pytester) -> None: pytester.makeconftest( """ - import py + # potentially avoid dependency on pylib + from _pytest.compat import legacy_path from pathlib import Path - collect_ignore = [py.path.local('hello'), 'test_world.py', Path('bye')] + collect_ignore = [legacy_path('hello'), 'test_world.py', Path('bye')] def pytest_addoption(parser): parser.addoption("--XX", action="store_true", default=False) def pytest_configure(config): @@ -1347,6 +1346,7 @@ def test_fscollector_from_parent(pytester: Pytester, request: FixtureRequest) -> Context: https://github.com/pytest-dev/pytest-cpp/pull/47 """ + from _pytest.compat import legacy_path class MyCollector(pytest.File): def __init__(self, *k, x, **kw): @@ -1354,7 +1354,7 @@ def __init__(self, *k, x, **kw): self.x = x collector = MyCollector.from_parent( - parent=request.session, fspath=py.path.local(pytester.path) / "foo", x=10 + parent=request.session, fspath=legacy_path(pytester.path) / "foo", x=10 ) assert collector.x == 10 diff --git a/testing/test_main.py b/testing/test_main.py index 2ed111895cd..4450029342e 100644 --- a/testing/test_main.py +++ b/testing/test_main.py @@ -205,7 +205,7 @@ def test_absolute_paths_are_resolved_correctly(self, invocation_path: Path) -> N def test_module_full_path_without_drive(pytester: Pytester) -> None: """Collect and run test using full path except for the drive letter (#7628). - Passing a full path without a drive letter would trigger a bug in py.path.local + Passing a full path without a drive letter would trigger a bug in legacy_path where it would keep the full path without the drive letter around, instead of resolving to the full path, resulting in fixtures node ids not matching against test node ids correctly. """ diff --git a/testing/test_nodes.py b/testing/test_nodes.py index 59d9f409eac..dde161777cd 100644 --- a/testing/test_nodes.py +++ b/testing/test_nodes.py @@ -3,10 +3,9 @@ from typing import List from typing import Type -import py - import pytest from _pytest import nodes +from _pytest.compat import legacy_path from _pytest.pytester import Pytester from _pytest.warning_types import PytestWarning @@ -77,7 +76,7 @@ class FakeSession1: session = cast(pytest.Session, FakeSession1) - assert nodes._check_initialpaths_for_relpath(session, py.path.local(cwd)) == "" + assert nodes._check_initialpaths_for_relpath(session, legacy_path(cwd)) == "" sub = cwd / "file" @@ -86,9 +85,9 @@ class FakeSession2: session = cast(pytest.Session, FakeSession2) - assert nodes._check_initialpaths_for_relpath(session, py.path.local(sub)) == "file" + assert nodes._check_initialpaths_for_relpath(session, legacy_path(sub)) == "file" - outside = py.path.local("/outside") + outside = legacy_path("/outside") assert nodes._check_initialpaths_for_relpath(session, outside) is None diff --git a/testing/test_parseopt.py b/testing/test_parseopt.py index c33337b67b3..6ba9269e564 100644 --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -4,9 +4,8 @@ import subprocess import sys -import py - import pytest +from _pytest.compat import legacy_path from _pytest.config import argparsing as parseopt from _pytest.config.exceptions import UsageError from _pytest.monkeypatch import MonkeyPatch @@ -124,11 +123,11 @@ def test_parse(self, parser: parseopt.Parser) -> None: assert not getattr(args, parseopt.FILE_OR_DIR) def test_parse2(self, parser: parseopt.Parser) -> None: - args = parser.parse([py.path.local()]) - assert getattr(args, parseopt.FILE_OR_DIR)[0] == py.path.local() + args = parser.parse([legacy_path(".")]) + assert getattr(args, parseopt.FILE_OR_DIR)[0] == legacy_path(".") def test_parse_known_args(self, parser: parseopt.Parser) -> None: - parser.parse_known_args([py.path.local()]) + parser.parse_known_args([legacy_path(".")]) parser.addoption("--hello", action="store_true") ns = parser.parse_known_args(["x", "--y", "--hello", "this"]) assert ns.hello diff --git a/testing/test_pathlib.py b/testing/test_pathlib.py index 48149084ece..d71e44e36b6 100644 --- a/testing/test_pathlib.py +++ b/testing/test_pathlib.py @@ -7,9 +7,8 @@ from types import ModuleType from typing import Generator -import py - import pytest +from _pytest.compat import legacy_path from _pytest.monkeypatch import MonkeyPatch from _pytest.pathlib import bestrelpath from _pytest.pathlib import commonpath @@ -28,14 +27,14 @@ class TestFNMatcherPort: """Test that our port of py.common.FNMatcher (fnmatch_ex) produces the - same results as the original py.path.local.fnmatch method.""" + same results as the original legacy_path.fnmatch method.""" @pytest.fixture(params=["pathlib", "py.path"]) def match(self, request): if request.param == "py.path": def match_(pattern, path): - return py.path.local(path).fnmatch(pattern) + return legacy_path(path).fnmatch(pattern) else: assert request.param == "pathlib" diff --git a/testing/test_reports.py b/testing/test_reports.py index b376f6198ae..3da63c2c873 100644 --- a/testing/test_reports.py +++ b/testing/test_reports.py @@ -1,11 +1,10 @@ from typing import Sequence from typing import Union -import py.path - import pytest from _pytest._code.code import ExceptionChainRepr from _pytest._code.code import ExceptionRepr +from _pytest.compat import legacy_path from _pytest.config import Config from _pytest.pytester import Pytester from _pytest.reports import CollectReport @@ -237,7 +236,7 @@ def test_a(): reports = reprec.getreports("pytest_runtest_logreport") assert len(reports) == 3 test_a_call = reports[1] - test_a_call.path1 = py.path.local(pytester.path) # type: ignore[attr-defined] + test_a_call.path1 = legacy_path(pytester.path) # type: ignore[attr-defined] test_a_call.path2 = pytester.path # type: ignore[attr-defined] data = test_a_call._to_json() assert data["path1"] == str(pytester.path) From 620e8196561e0d8b359b25f1c494ea0002d3cb3b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 6 Mar 2021 22:59:33 +0100 Subject: [PATCH 0160/2772] Add enterPy training (#8396) --- doc/en/index.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/en/index.rst b/doc/en/index.rst index 084725ec2c1..7f74534027c 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -1,5 +1,11 @@ :orphan: +.. sidebar:: Next Open Trainings + + - `Professionelles Testen für Python mit pytest `_ (German), part of the enterPy conference, April 22nd, remote. + + Also see `previous talks and blogposts `_. + .. _features: pytest: helps you write better programs From f0ad73c4b05b4307e3dbf890f4e2772ea73e0a2e Mon Sep 17 00:00:00 2001 From: pytest bot Date: Sun, 7 Mar 2021 00:48:50 +0000 Subject: [PATCH 0161/2772] [automated] Update plugin list --- doc/en/plugin_list.rst | 43 ++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/doc/en/plugin_list.rst b/doc/en/plugin_list.rst index f50b6033ff1..8426f469c94 100644 --- a/doc/en/plugin_list.rst +++ b/doc/en/plugin_list.rst @@ -3,7 +3,7 @@ Plugins List PyPI projects that match "pytest-\*" are considered plugins and are listed automatically. Packages classified as inactive are excluded. -This list contains 833 plugins. +This list contains 836 plugins. ============================================================================================================== ======================================================================================================================================================================== ============== ===================== ============================================ name summary last release status requires @@ -11,7 +11,7 @@ name `pytest-adaptavist `_ pytest plugin for generating test execution results within Jira Test Management (tm4j) Feb 05, 2020 N/A pytest (>=3.4.1) `pytest-adf `_ Pytest plugin for writing Azure Data Factory integration tests Jun 03, 2020 4 - Beta pytest (>=3.5.0) `pytest-aggreport `_ pytest plugin for pytest-repeat that generate aggregate report of the same test cases with additional statistics details. Jul 19, 2019 4 - Beta pytest (>=4.3.1) -`pytest-aio `_ Pytest plugin for testing async python code Feb 27, 2021 4 - Beta pytest ; extra == 'tests' +`pytest-aio `_ Pytest plugin for testing async python code Mar 02, 2021 4 - Beta pytest ; extra == 'tests' `pytest-aiofiles `_ pytest fixtures for writing aiofiles tests with pyfakefs May 14, 2017 5 - Production/Stable N/A `pytest-aiohttp `_ pytest plugin for aiohttp support Dec 05, 2017 N/A pytest `pytest-aiohttp-client `_ Pytest `client` fixture for the Aiohttp Nov 01, 2020 N/A pytest (>=6) @@ -152,8 +152,9 @@ name `pytest-curl-report `_ pytest plugin to generate curl command line report Dec 11, 2016 4 - Beta N/A `pytest-custom-concurrency `_ Custom grouping concurrence for pytest Feb 08, 2021 N/A N/A `pytest-custom-exit-code `_ Exit pytest test session with custom exit code in different scenarios Aug 07, 2019 4 - Beta pytest (>=4.0.2) +`pytest-custom-nodeid `_ Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report Mar 02, 2021 N/A N/A `pytest-custom-report `_ Configure the symbols displayed for test outcomes Jan 30, 2019 N/A pytest -`pytest-custom-scheduling `_ Custom grouping for pytest-xdist Feb 22, 2021 N/A N/A +`pytest-custom-scheduling `_ Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report Mar 01, 2021 N/A N/A `pytest-cython `_ A plugin for testing Cython extension modules Jan 26, 2021 4 - Beta pytest (>=2.7.3) `pytest-darker `_ A pytest plugin for checking of modified code using Darker Aug 16, 2020 N/A pytest (>=6.0.1) ; extra == 'test' `pytest-dash `_ pytest fixtures to run dash applications. Mar 18, 2019 N/A N/A @@ -200,7 +201,7 @@ name `pytest-django-lite `_ The bare minimum to integrate py.test with Django. Jan 30, 2014 N/A N/A `pytest-django-model `_ A Simple Way to Test your Django Models Feb 14, 2019 4 - Beta N/A `pytest-django-ordering `_ A pytest plugin for preserving the order in which Django runs tests. Jul 25, 2019 5 - Production/Stable pytest (>=2.3.0) -`pytest-django-queries `_ Generate performance reports from your django database performance tests. Sep 03, 2020 N/A N/A +`pytest-django-queries `_ Generate performance reports from your django database performance tests. Mar 01, 2021 N/A N/A `pytest-djangorestframework `_ A djangorestframework plugin for pytest Aug 11, 2019 4 - Beta N/A `pytest-django-rq `_ A pytest plugin to help writing unit test for django-rq Apr 13, 2020 4 - Beta N/A `pytest-django-sqlcounts `_ py.test plugin for reporting the number of SQLs executed per django testcase. Jun 16, 2015 4 - Beta N/A @@ -216,8 +217,8 @@ name `pytest-docker-pexpect `_ pytest plugin for writing functional tests with pexpect and docker Jan 14, 2019 N/A pytest `pytest-docker-postgresql `_ A simple plugin to use with pytest Sep 24, 2019 4 - Beta pytest (>=3.5.0) `pytest-docker-py `_ Easy to use, simple to extend, pytest plugin that minimally leverages docker-py. Nov 27, 2018 N/A pytest (==4.0.0) -`pytest-docker-registry-fixtures `_ Pytest fixtures for testing with docker registries. Feb 17, 2021 4 - Beta pytest -`pytest-docker-tools `_ Docker integration tests for pytest Jan 15, 2021 4 - Beta pytest (>=6.0.1,<7.0.0) +`pytest-docker-registry-fixtures `_ Pytest fixtures for testing with docker registries. Mar 04, 2021 4 - Beta pytest +`pytest-docker-tools `_ Docker integration tests for pytest Mar 02, 2021 4 - Beta pytest (>=6.0.1,<7.0.0) `pytest-docs `_ Documentation tool for pytest Nov 11, 2018 4 - Beta pytest (>=3.5.0) `pytest-docstyle `_ pytest plugin to run pydocstyle Mar 23, 2020 3 - Alpha N/A `pytest-doctest-custom `_ A py.test plugin for customizing string representations of doctest results. Jul 25, 2016 4 - Beta N/A @@ -274,6 +275,7 @@ name `pytest-factoryboy `_ Factory Boy support for pytest. Dec 30, 2020 6 - Mature pytest (>=4.6) `pytest-factoryboy-fixtures `_ Generates pytest fixtures that allow the use of type hinting Jun 25, 2020 N/A N/A `pytest-factoryboy-state `_ Simple factoryboy random state management Dec 11, 2020 4 - Beta pytest (>=5.0) +`pytest-failed-screenshot `_ Test case fails,take a screenshot,save it,attach it to the allure Feb 28, 2021 N/A N/A `pytest-failed-to-verify `_ A pytest plugin that helps better distinguishing real test failures from setup flakiness. Aug 08, 2019 5 - Production/Stable pytest (>=4.1.0) `pytest-faker `_ Faker integration with the pytest framework. Dec 19, 2016 6 - Mature N/A `pytest-falcon `_ Pytest helpers for Falcon. Sep 07, 2016 4 - Beta N/A @@ -321,11 +323,11 @@ name `pytest-girder `_ A set of pytest fixtures for testing Girder applications. Feb 25, 2021 N/A N/A `pytest-git `_ Git repository fixture for py.test May 28, 2019 5 - Production/Stable pytest `pytest-gitcov `_ Pytest plugin for reporting on coverage of the last git commit. Jan 11, 2020 2 - Pre-Alpha N/A -`pytest-git-fixtures `_ Pytest fixtures for testing with git. Jan 25, 2021 4 - Beta pytest +`pytest-git-fixtures `_ Pytest fixtures for testing with git. Mar 04, 2021 4 - Beta pytest `pytest-github `_ Plugin for py.test that associates tests with github issues using a marker. Mar 07, 2019 5 - Production/Stable N/A `pytest-github-actions-annotate-failures `_ pytest plugin to annotate failed tests with a workflow command for GitHub Actions Oct 13, 2020 N/A pytest (>=4.0.0) `pytest-gitignore `_ py.test plugin to ignore the same files as git Jul 17, 2015 4 - Beta N/A -`pytest-gnupg-fixtures `_ Pytest fixtures for testing with gnupg. Feb 22, 2021 4 - Beta pytest +`pytest-gnupg-fixtures `_ Pytest fixtures for testing with gnupg. Mar 04, 2021 4 - Beta pytest `pytest-golden `_ Plugin for pytest that offloads expected outputs to data files Nov 23, 2020 N/A pytest (>=6.1.2,<7.0.0) `pytest-graphql-schema `_ Get graphql schema as fixture for pytest Oct 18, 2019 N/A N/A `pytest-greendots `_ Green progress dots Feb 08, 2014 3 - Alpha N/A @@ -342,8 +344,9 @@ name `pytest-historic `_ Custom report to display pytest historical execution records Apr 08, 2020 N/A pytest `pytest-historic-hook `_ Custom listener to store execution results into MYSQL DB, which is used for pytest-historic report Apr 08, 2020 N/A pytest `pytest-homeassistant `_ A pytest plugin for use with homeassistant custom components. Aug 12, 2020 4 - Beta N/A -`pytest-homeassistant-custom-component `_ Experimental package to automatically extract test plugins for Home Assistant custom components Feb 10, 2021 3 - Alpha pytest (==6.2.2) +`pytest-homeassistant-custom-component `_ Experimental package to automatically extract test plugins for Home Assistant custom components Mar 03, 2021 3 - Alpha pytest (==6.2.2) `pytest-honors `_ Report on tests that honor constraints, and guard against regressions Mar 06, 2020 4 - Beta N/A +`pytest-hoverfly `_ Simplify working with Hoverfly from pytest Mar 04, 2021 N/A pytest (>=5.0) `pytest-hoverfly-wrapper `_ Integrates the Hoverfly HTTP proxy into Pytest Jan 31, 2021 4 - Beta N/A `pytest-html `_ pytest plugin for generating HTML reports Dec 13, 2020 5 - Production/Stable pytest (!=6.0.0,>=5.0) `pytest-html-lee `_ optimized pytest plugin for generating HTML reports Jun 30, 2020 5 - Production/Stable pytest (>=5.0) @@ -355,7 +358,7 @@ name `pytest-http-mocker `_ Pytest plugin for http mocking (via https://github.com/vilus/mocker) Oct 20, 2019 N/A N/A `pytest-httpretty `_ A thin wrapper of HTTPretty for pytest Feb 16, 2014 3 - Alpha N/A `pytest-httpserver `_ pytest-httpserver is a httpserver for pytest Feb 14, 2021 3 - Alpha pytest ; extra == 'dev' -`pytest-httpx `_ Send responses to httpx. Nov 25, 2020 5 - Production/Stable pytest (==6.*) +`pytest-httpx `_ Send responses to httpx. Mar 01, 2021 5 - Production/Stable pytest (==6.*) `pytest-hue `_ Visualise PyTest status via your Phillips Hue lights May 09, 2019 N/A N/A `pytest-hypo-25 `_ help hypo module for pytest Jan 12, 2020 3 - Alpha N/A `pytest-ibutsu `_ A plugin to sent pytest results to an Ibutsu server Feb 24, 2021 4 - Beta pytest @@ -371,7 +374,7 @@ name `pytest-inmanta `_ A py.test plugin providing fixtures to simplify inmanta modules testing. Oct 12, 2020 5 - Production/Stable N/A `pytest-inmanta-extensions `_ Inmanta tests package Jan 07, 2021 5 - Production/Stable N/A `pytest-Inomaly `_ A simple image diff plugin for pytest Feb 13, 2018 4 - Beta N/A -`pytest-insta `_ A practical snapshot testing plugin for pytest Feb 25, 2021 N/A pytest (>=6.0.2,<7.0.0) +`pytest-insta `_ A practical snapshot testing plugin for pytest Mar 03, 2021 N/A pytest (>=6.0.2,<7.0.0) `pytest-instafail `_ pytest plugin to show failures instantly Jun 14, 2020 4 - Beta pytest (>=2.9) `pytest-instrument `_ pytest plugin to instrument tests Apr 05, 2020 5 - Production/Stable pytest (>=5.1.0) `pytest-integration `_ Organizing pytests by integration or not Apr 16, 2020 N/A N/A @@ -642,29 +645,29 @@ name `pytest-roast `_ pytest plugin for ROAST configuration override and fixtures Feb 05, 2021 5 - Production/Stable pytest (<6) `pytest-rotest `_ Pytest integration with rotest Sep 08, 2019 N/A pytest (>=3.5.0) `pytest-rpc `_ Extend py.test for RPC OpenStack testing. Feb 22, 2019 4 - Beta pytest (~=3.6) -`pytest-rt `_ pytest data collector plugin for Testgr Jan 24, 2021 N/A N/A -`pytest-rts `_ Coverage-based regression test selection (RTS) plugin for pytest Feb 15, 2021 N/A pytest +`pytest-rt `_ pytest data collector plugin for Testgr Mar 03, 2021 N/A N/A +`pytest-rts `_ Coverage-based regression test selection (RTS) plugin for pytest Mar 03, 2021 N/A pytest `pytest-runfailed `_ implement a --failed option for pytest Mar 24, 2016 N/A N/A `pytest-runner `_ Invoke py.test as distutils command with dependency resolution Feb 12, 2021 5 - Production/Stable pytest (!=3.7.3,>=3.5) ; extra == 'testing' `pytest-salt `_ Pytest Salt Plugin Jan 27, 2020 4 - Beta N/A `pytest-salt-containers `_ A Pytest plugin that builds and creates docker containers Nov 09, 2016 4 - Beta N/A -`pytest-salt-factories `_ Pytest Salt Plugin Feb 19, 2021 4 - Beta pytest (>=6.1.1) +`pytest-salt-factories `_ Pytest Salt Plugin Mar 05, 2021 4 - Beta pytest (>=6.1.1) `pytest-salt-from-filenames `_ Simple PyTest Plugin For Salt's Test Suite Specifically Jan 29, 2019 4 - Beta pytest (>=4.1) `pytest-salt-runtests-bridge `_ Simple PyTest Plugin For Salt's Test Suite Specifically Dec 05, 2019 4 - Beta pytest (>=4.1) `pytest-sanic `_ a pytest plugin for Sanic Feb 27, 2021 N/A pytest (>=5.2) `pytest-sanity `_ Dec 07, 2020 N/A N/A `pytest-sa-pg `_ May 14, 2019 N/A N/A -`pytest-sbase `_ A complete web automation framework for end-to-end testing. Feb 27, 2021 5 - Production/Stable N/A +`pytest-sbase `_ A complete web automation framework for end-to-end testing. Mar 06, 2021 5 - Production/Stable N/A `pytest-scenario `_ pytest plugin for test scenarios Feb 06, 2017 3 - Alpha N/A `pytest-schema `_ 👍 Validate return values against a schema-like object in testing Aug 31, 2020 5 - Production/Stable pytest (>=3.5.0) `pytest-securestore `_ An encrypted password store for use within pytest cases Jun 19, 2019 4 - Beta N/A `pytest-select `_ A pytest plugin which allows to (de-)select tests from a file. Jan 18, 2019 3 - Alpha pytest (>=3.0) `pytest-selenium `_ pytest plugin for Selenium Sep 19, 2020 5 - Production/Stable pytest (>=5.0.0) -`pytest-seleniumbase `_ A complete web automation framework for end-to-end testing. Feb 27, 2021 5 - Production/Stable N/A +`pytest-seleniumbase `_ A complete web automation framework for end-to-end testing. Mar 06, 2021 5 - Production/Stable N/A `pytest-selenium-enhancer `_ pytest plugin for Selenium Nov 26, 2020 5 - Production/Stable N/A `pytest-selenium-pdiff `_ A pytest package implementing perceptualdiff for Selenium tests. Apr 06, 2017 2 - Pre-Alpha N/A `pytest-send-email `_ Send pytest execution result email Dec 04, 2019 N/A N/A -`pytest-sentry `_ A pytest plugin to send testrun information to Sentry.io Dec 16, 2020 N/A N/A +`pytest-sentry `_ A pytest plugin to send testrun information to Sentry.io Mar 02, 2021 N/A N/A `pytest-server-fixtures `_ Extensible server fixures for py.test May 28, 2019 5 - Production/Stable pytest `pytest-serverless `_ Automatically mocks resources from serverless.yml in pytest using moto. Feb 20, 2021 4 - Beta N/A `pytest-services `_ Services plugin for pytest testing framework Oct 30, 2020 6 - Mature N/A @@ -726,7 +729,7 @@ name `pytest-stubprocess `_ Provide stub implementations for subprocesses in Python tests Sep 17, 2018 3 - Alpha pytest (>=3.5.0) `pytest-study `_ A pytest plugin to organize long run tests (named studies) without interfering the regular tests Sep 26, 2017 3 - Alpha pytest (>=2.0) `pytest-subprocess `_ A plugin to fake subprocess for pytest Aug 22, 2020 5 - Production/Stable pytest (>=4.0.0) -`pytest-subtesthack `_ A hack to explicitly set up and tear down fixtures. Jan 31, 2016 N/A N/A +`pytest-subtesthack `_ A hack to explicitly set up and tear down fixtures. Mar 02, 2021 N/A N/A `pytest-subtests `_ unittest subTest() support and subtests fixture Dec 13, 2020 4 - Beta pytest (>=5.3.0) `pytest-subunit `_ pytest-subunit is a plugin for py.test which outputs testsresult in subunit format. Aug 29, 2017 N/A N/A `pytest-sugar `_ pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly). Jul 06, 2020 3 - Alpha N/A @@ -827,10 +830,10 @@ name `pytest-xfiles `_ Pytest fixtures providing data read from function, module or package related (x)files. Feb 27, 2018 N/A N/A `pytest-xlog `_ Extended logging for test and decorators May 31, 2020 4 - Beta N/A `pytest-xpara `_ An extended parametrizing plugin of pytest. Oct 30, 2017 3 - Alpha pytest -`pytest-xprocess `_ A pytest plugin for managing processes across test runs. Nov 26, 2020 4 - Beta pytest (>=2.8) +`pytest-xprocess `_ A pytest plugin for managing processes across test runs. Mar 02, 2021 4 - Beta pytest (>=2.8) `pytest-xray `_ May 30, 2019 3 - Alpha N/A `pytest-xrayjira `_ Mar 17, 2020 3 - Alpha pytest (==4.3.1) -`pytest-xray-server `_ Nov 29, 2020 3 - Alpha N/A +`pytest-xray-server `_ Mar 03, 2021 3 - Alpha N/A `pytest-xvfb `_ A pytest plugin to run Xvfb for tests. Jun 09, 2020 4 - Beta pytest (>=2.8.1) `pytest-yaml `_ This plugin is used to load yaml output to your test using pytest framework. Oct 05, 2018 N/A pytest `pytest-yamltree `_ Create or check file/directory trees described by YAML Mar 02, 2020 4 - Beta pytest (>=3.1.1) From 412fc001a0770d44b281d9b39f736df3cdc80c37 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Sun, 7 Mar 2021 14:57:19 +0100 Subject: [PATCH 0162/2772] fix bug in test for issue 519 assert the actual outcome and fix the filename --- changelog/8411.trivial.rst | 1 + testing/example_scripts/issue_519.py | 4 ++-- testing/examples/test_issue519.py | 5 +++-- 3 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 changelog/8411.trivial.rst diff --git a/changelog/8411.trivial.rst b/changelog/8411.trivial.rst new file mode 100644 index 00000000000..8f169a900fa --- /dev/null +++ b/changelog/8411.trivial.rst @@ -0,0 +1 @@ +Assert the outcomes for the issue 518 test and fix the test. diff --git a/testing/example_scripts/issue_519.py b/testing/example_scripts/issue_519.py index 3928294886f..e44367fca04 100644 --- a/testing/example_scripts/issue_519.py +++ b/testing/example_scripts/issue_519.py @@ -20,12 +20,12 @@ def checked_order(): yield order pprint.pprint(order) assert order == [ - ("testing/example_scripts/issue_519.py", "fix1", "arg1v1"), + ("issue_519.py", "fix1", "arg1v1"), ("test_one[arg1v1-arg2v1]", "fix2", "arg2v1"), ("test_two[arg1v1-arg2v1]", "fix2", "arg2v1"), ("test_one[arg1v1-arg2v2]", "fix2", "arg2v2"), ("test_two[arg1v1-arg2v2]", "fix2", "arg2v2"), - ("testing/example_scripts/issue_519.py", "fix1", "arg1v2"), + ("issue_519.py", "fix1", "arg1v2"), ("test_one[arg1v2-arg2v1]", "fix2", "arg2v1"), ("test_two[arg1v2-arg2v1]", "fix2", "arg2v1"), ("test_one[arg1v2-arg2v2]", "fix2", "arg2v2"), diff --git a/testing/examples/test_issue519.py b/testing/examples/test_issue519.py index 85ba545e671..7b9c109889e 100644 --- a/testing/examples/test_issue519.py +++ b/testing/examples/test_issue519.py @@ -1,6 +1,7 @@ from _pytest.pytester import Pytester -def test_510(pytester: Pytester) -> None: +def test_519(pytester: Pytester) -> None: pytester.copy_example("issue_519.py") - pytester.runpytest("issue_519.py") + res = pytester.runpytest("issue_519.py") + res.assert_outcomes(passed=8) From 125633728f4215c59a5e0da2985f888a2a945325 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Mar 2021 16:54:31 +0000 Subject: [PATCH 0163/2772] [pre-commit.ci] pre-commit autoupdate --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 24196151952..83a50be93c4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,7 @@ repos: - id: black args: [--safe, --quiet] - repo: https://github.com/asottile/blacken-docs - rev: v1.9.2 + rev: v1.10.0 hooks: - id: blacken-docs additional_dependencies: [black==20.8b1] From ddde3266c6ec0b198e9e2ae68bd1692baf329071 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Mar 2021 16:55:53 +0000 Subject: [PATCH 0164/2772] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- testing/test_doctest.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/testing/test_doctest.py b/testing/test_doctest.py index b63665349a1..1d33e737830 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -730,12 +730,11 @@ def test_unicode_doctest(self, pytester: Pytester): test_unicode_doctest=""" .. doctest:: - >>> print( - ... "Hi\\n\\nByé") + >>> print("Hi\\n\\nByé") Hi ... Byé - >>> 1/0 # Byé + >>> 1 / 0 # Byé 1 """ ) From 7a6ec5616d071e3b21f0ce1c07c66388b3cce81a Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 8 Mar 2021 19:12:08 -0800 Subject: [PATCH 0165/2772] clean up mkdtemp usage Committed via https://github.com/asottile/all-repos --- doc/en/fixture.rst | 12 +++++------- testing/test_assertrewrite.py | 6 ++---- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index 028786f6523..50bc9ee8e34 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -2228,7 +2228,6 @@ file: # content of conftest.py import os - import shutil import tempfile import pytest @@ -2236,12 +2235,11 @@ file: @pytest.fixture def cleandir(): - old_cwd = os.getcwd() - newpath = tempfile.mkdtemp() - os.chdir(newpath) - yield - os.chdir(old_cwd) - shutil.rmtree(newpath) + with tempfile.TemporaryDirectory() as newpath: + old_cwd = os.getcwd() + os.chdir(newpath) + yield + os.chdir(old_cwd) and declare its use in a test module via a ``usefixtures`` marker: diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index cba03406e86..f7d9d62ef63 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -1395,12 +1395,10 @@ def test_cwd_changed(self, pytester: Pytester, monkeypatch) -> None: **{ "test_setup_nonexisting_cwd.py": """\ import os - import shutil import tempfile - d = tempfile.mkdtemp() - os.chdir(d) - shutil.rmtree(d) + with tempfile.TemporaryDirectory() as d: + os.chdir(d) """, "test_test.py": """\ def test(): From dbed1ff68fce457c8dd94e2926dd296b0c1c5ca0 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 9 Mar 2021 21:56:16 +0100 Subject: [PATCH 0166/2772] adopt main terminology in the configs ref pytest-dev/meta#8 --- .github/workflows/main.yml | 8 ++++---- CONTRIBUTING.rst | 4 ++-- README.rst | 10 +++++----- doc/en/development_guide.rst | 2 +- doc/en/funcarg_compare.rst | 2 +- doc/en/index.rst | 4 ++-- doc/en/license.rst | 2 +- testing/test_config.py | 2 +- tox.ini | 4 ++-- 9 files changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a3ea24b7cb0..7872f978ae1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,7 +3,7 @@ name: main on: push: branches: - - master + - main - "[0-9]+.[0-9]+.x" tags: - "[0-9]+.[0-9]+.[0-9]+" @@ -11,7 +11,7 @@ on: pull_request: branches: - - master + - main - "[0-9]+.[0-9]+.x" jobs: @@ -56,7 +56,7 @@ jobs: - name: "windows-py37-pluggy" python: "3.7" os: windows-latest - tox_env: "py37-pluggymaster-xdist" + tox_env: "py37-pluggymain-xdist" - name: "windows-py38" python: "3.8" os: windows-latest @@ -75,7 +75,7 @@ jobs: - name: "ubuntu-py37-pluggy" python: "3.7" os: ubuntu-latest - tox_env: "py37-pluggymaster-xdist" + tox_env: "py37-pluggymain-xdist" - name: "ubuntu-py37-freeze" python: "3.7" os: ubuntu-latest diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 054f809a818..ba783d5c106 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -236,7 +236,7 @@ Here is a simple overview, with pytest-specific bits: $ cd pytest # now, create your own branch off "master": - $ git checkout -b your-bugfix-branch-name master + $ git checkout -b your-bugfix-branch-name main Given we have "major.minor.micro" version numbers, bug fixes will usually be released in micro releases whereas features will be released in @@ -318,7 +318,7 @@ Here is a simple overview, with pytest-specific bits: compare: your-branch-name base-fork: pytest-dev/pytest - base: master + base: main Writing Tests diff --git a/README.rst b/README.rst index 159bf1d4f75..d0014e8bfff 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,4 @@ -.. image:: https://github.com/pytest-dev/pytest/raw/master/doc/en/img/pytest_logo_curves.svg +.. image:: https://github.com/pytest-dev/pytest/raw/main/doc/en/img/pytest_logo_curves.svg :target: https://docs.pytest.org/en/stable/ :align: center :height: 200 @@ -16,14 +16,14 @@ .. image:: https://img.shields.io/pypi/pyversions/pytest.svg :target: https://pypi.org/project/pytest/ -.. image:: https://codecov.io/gh/pytest-dev/pytest/branch/master/graph/badge.svg +.. image:: https://codecov.io/gh/pytest-dev/pytest/branch/main/graph/badge.svg :target: https://codecov.io/gh/pytest-dev/pytest :alt: Code coverage Status .. image:: https://github.com/pytest-dev/pytest/workflows/main/badge.svg :target: https://github.com/pytest-dev/pytest/actions?query=workflow%3Amain -.. image:: https://results.pre-commit.ci/badge/github/pytest-dev/pytest/master.svg +.. image:: https://results.pre-commit.ci/badge/github/pytest-dev/pytest/main.svg :target: https://results.pre-commit.ci/latest/github/pytest-dev/pytest/master :alt: pre-commit.ci status @@ -151,8 +151,8 @@ Tidelift will coordinate the fix and disclosure. License ------- -Copyright Holger Krekel and others, 2004-2020. +Copyright Holger Krekel and others, 2004-2021. Distributed under the terms of the `MIT`_ license, pytest is free and open source software. -.. _`MIT`: https://github.com/pytest-dev/pytest/blob/master/LICENSE +.. _`MIT`: https://github.com/pytest-dev/pytest/blob/main/LICENSE diff --git a/doc/en/development_guide.rst b/doc/en/development_guide.rst index 77076d4834e..3ee0ebbc239 100644 --- a/doc/en/development_guide.rst +++ b/doc/en/development_guide.rst @@ -4,4 +4,4 @@ Development Guide The contributing guidelines are to be found :ref:`here `. The release procedure for pytest is documented on -`GitHub `_. +`GitHub `_. diff --git a/doc/en/funcarg_compare.rst b/doc/en/funcarg_compare.rst index 5e2a050063c..0f44d1da05c 100644 --- a/doc/en/funcarg_compare.rst +++ b/doc/en/funcarg_compare.rst @@ -168,7 +168,7 @@ pytest for a long time offered a pytest_configure and a pytest_sessionstart hook which are often used to setup global resources. This suffers from several problems: -1. in distributed testing the master process would setup test resources +1. in distributed testing the managing process would setup test resources that are never needed because it only co-ordinates the test run activities of the worker processes. diff --git a/doc/en/index.rst b/doc/en/index.rst index 7f74534027c..cfe3a271ede 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -129,8 +129,8 @@ Tidelift will coordinate the fix and disclosure. License ------- -Copyright Holger Krekel and others, 2004-2020. +Copyright Holger Krekel and others, 2004-2021. Distributed under the terms of the `MIT`_ license, pytest is free and open source software. -.. _`MIT`: https://github.com/pytest-dev/pytest/blob/master/LICENSE +.. _`MIT`: https://github.com/pytest-dev/pytest/blob/main/LICENSE diff --git a/doc/en/license.rst b/doc/en/license.rst index c6c10bbf358..13765be1595 100644 --- a/doc/en/license.rst +++ b/doc/en/license.rst @@ -29,4 +29,4 @@ Distributed under the terms of the `MIT`_ license, pytest is free and open sourc OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -.. _`MIT`: https://github.com/pytest-dev/pytest/blob/master/LICENSE +.. _`MIT`: https://github.com/pytest-dev/pytest/blob/main/LICENSE diff --git a/testing/test_config.py b/testing/test_config.py index 8c1441e0680..b23264e1d79 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -1780,7 +1780,7 @@ class DummyPlugin: ) def test_config_blocked_default_plugins(pytester: Pytester, plugin: str) -> None: if plugin == "debugging": - # Fixed in xdist master (after 1.27.0). + # Fixed in xdist (after 1.27.0). # https://github.com/pytest-dev/pytest-xdist/pull/422 try: import xdist # noqa: F401 diff --git a/tox.ini b/tox.ini index 908f56ea681..da5a634de8b 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,7 @@ envlist = py38 py39 pypy3 - py37-{pexpect,xdist,unittestextras,numpy,pluggymaster} + py37-{pexpect,xdist,unittestextras,numpy,pluggymain} doctesting plugins py37-freeze @@ -45,7 +45,7 @@ deps = doctesting: PyYAML numpy: numpy>=1.19.4 pexpect: pexpect>=4.8.0 - pluggymaster: git+https://github.com/pytest-dev/pluggy.git@master + pluggymain: pluggy @ git+https://github.com/pytest-dev/pluggy.git pygments>=2.7.2 unittestextras: twisted unittestextras: asynctest From 4a19533ba5f65f839b6bd0fe307a053049230796 Mon Sep 17 00:00:00 2001 From: Daniele Procida Date: Wed, 10 Mar 2021 18:27:30 +0000 Subject: [PATCH 0167/2772] Renamed Install to Getting started; moved notes to index --- doc/en/_templates/globaltoc.html | 2 +- doc/en/getting-started.rst | 18 +++++------------- doc/en/index.rst | 17 +++++++++++++---- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/doc/en/_templates/globaltoc.html b/doc/en/_templates/globaltoc.html index 5fc1ea13e5b..25666d8db78 100644 --- a/doc/en/_templates/globaltoc.html +++ b/doc/en/_templates/globaltoc.html @@ -2,7 +2,7 @@

    {{ _('Table Of Contents') }}