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) tmp_path.joinpath("a").rename(tmp_path.joinpath("b")) with pytest.raises(ImportPathMismatchError): import_path(tmp_path.joinpath("b", "test_x123.py")) # Errors can be ignored. monkeypatch.setenv("PY_IGNORE_IMPORTMISMATCH", "1") 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(tmp_path.joinpath("b", "test_x123.py"))
def test_importmode_importlib_with_dataclass(self, tmp_path: Path) -> None: """Ensure that importlib mode works with a module containing dataclasses (#7856).""" fn = tmp_path.joinpath("_src/tests/test_dataclass.py") fn.parent.mkdir(parents=True) fn.write_text( dedent(""" from dataclasses import dataclass @dataclass class Data: value: str """)) module = import_path(fn, mode="importlib", root=tmp_path) Data: Any = getattr(module, "Data") data = Data(value="foo") assert data.value == "foo" assert data.__module__ == "_src.tests.test_dataclass"
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
def test_importmode_importlib_with_pickle(self, tmp_path: Path) -> None: """Ensure that importlib mode works with pickle (#7859).""" fn = tmp_path.joinpath("_src/tests/test_pickle.py") fn.parent.mkdir(parents=True) fn.write_text( dedent(""" import pickle def _action(): return 42 def round_trip(): s = pickle.dumps(_action) return pickle.loads(s) """)) module = import_path(fn, mode="importlib", root=tmp_path) round_trip = getattr(module, "round_trip") action = round_trip() assert action() == 42
def _importconftest( self, conftestpath: py.path.local, 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() with contextlib.suppress(KeyError): return self._conftestpath2mod[key] pkgpath = conftestpath.pypkgpath() if pkgpath is None: _ensure_removed_sysmodule(conftestpath.purebasename) try: mod = import_path(conftestpath, mode=importmode) except Exception as e: assert e.__traceback__ is not None exc_info = (type(e), e, e.__traceback__) raise ConftestImportFailure(conftestpath, exc_info) from e self._check_non_top_pytest_plugins(mod, conftestpath) self._conftest_plugins.add(mod) self._conftestpath2mod[key] = mod dirpath = conftestpath.dirpath() if dirpath in self._dirpath2confmods: for path, mods in self._dirpath2confmods.items(): if path and path.relto(dirpath) or path == dirpath: assert mod not in mods mods.append(mod) self.trace("loading conftestmodule {!r}".format(mod)) self.consider_conftest(mod) return mod
def 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 str(simple_module.parent) not in sys.path
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_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")) assert mod.stuff == "got it" # type: ignore[attr-defined] assert mod.__name__ == "otherdir.b"
def test_a(self, path1): otherdir = path1.join("otherdir") mod = import_path(otherdir.join("a.py")) assert mod.result == "got it" # type: ignore[attr-defined] assert mod.__name__ == "otherdir.a"
def test_messy_name(self, tmpdir): # http://bitbucket.org/hpk42/py-trunk/issue/129 path = tmpdir.ensure("foo__init__.py") module = import_path(path) assert module.__name__ == "foo__init__"
def test_smoke_test(self, path1): obj = import_path(path1.join("execfile.py")) assert obj.x == 42 # type: ignore[attr-defined] assert obj.__name__ == "execfile"
def collect(self): # When running directly from pytest we need to make sure that we # don't accidentally import setup.py! if PYTEST_GE_6_3: fspath = self.path filepath = self.path.name else: fspath = self.fspath filepath = self.fspath.basename if filepath == "setup.py": return elif filepath == "conftest.py": if PYTEST_GE_7_0: module = self.config.pluginmanager._importconftest( self.path, self.config.getoption("importmode"), rootpath=self.config.rootpath) elif PYTEST_GE_6_3: module = self.config.pluginmanager._importconftest( self.path, self.config.getoption("importmode")) elif PYTEST_GT_5: module = self.config.pluginmanager._importconftest( self.fspath, self.config.getoption("importmode")) else: module = self.config.pluginmanager._importconftest( self.fspath) else: try: if PYTEST_GT_5: from _pytest.pathlib import import_path if PYTEST_GE_6_3: module = import_path(fspath, root=self.config.rootpath) elif PYTEST_GT_5: module = import_path(fspath) else: module = fspath.pyimport() except ImportError: if self.config.getvalue("doctest_ignore_import_errors"): pytest.skip("unable to import module %r" % fspath) else: raise options = get_optionflags(self) | FIX # uses internal doctest module parsing mechanism finder = DocTestFinderPlus() runner = doctest.DebugRunner(verbose=False, optionflags=options, checker=OutputChecker()) for test in finder.find(module): if test.examples: # skip empty doctests ignore_warnings_context_needed = False show_warnings_context_needed = False for example in test.examples: if (config.getoption('remote_data', 'none') != 'any' and example.options.get(REMOTE_DATA)): example.options[doctest.SKIP] = True # If warnings are to be ignored we need to catch them by # wrapping the source in a context manager. elif example.options.get(IGNORE_WARNINGS, False): example.source = ( "with _doctestplus_ignore_all_warnings():\n" + indent(example.source, ' ')) ignore_warnings_context_needed = True # Same for SHOW_WARNINGS elif example.options.get(SHOW_WARNINGS, False): example.source = ( "with _doctestplus_show_all_warnings():\n" + indent(example.source, ' ')) show_warnings_context_needed = True # We insert the definition of the context manager to ignore # warnings at the start of the file if needed. if ignore_warnings_context_needed: test.examples.insert( 0, doctest.Example(source=IGNORE_WARNINGS_CONTEXT, want='')) if show_warnings_context_needed: test.examples.insert( 0, doctest.Example(source=SHOW_WARNINGS_CONTEXT, want='')) try: yield doctest_plugin.DoctestItem.from_parent( self, name=test.name, runner=runner, dtest=test) except AttributeError: # pytest < 5.4 yield doctest_plugin.DoctestItem( test.name, self, runner, test)
def test_import_path_missing_file(self, path1: Path) -> None: with pytest.raises(ImportPathMismatchError): import_path(path1 / "sampledir", root=path1)
def collect(self) -> Iterable[IPDoctestItem]: import doctest from .ipdoctest import DocTestFinder, IPDocTestParser class MockAwareDocTestFinder(DocTestFinder): """A hackish ipdoctest finder that overrides stdlib internals to fix a stdlib bug. https://github.com/pytest-dev/pytest/issues/3456 https://bugs.python.org/issue25532 """ def _find_lineno(self, obj, source_lines): """Doctest code does not take into account `@property`, this is a hackish way to fix it. https://bugs.python.org/issue17446 Wrapped Doctests will need to be unwrapped so the correct line number is returned. This will be reported upstream. #8796 """ if isinstance(obj, property): obj = getattr(obj, "fget", obj) if hasattr(obj, "__wrapped__"): # Get the main obj in case of it being wrapped obj = inspect.unwrap(obj) # Type ignored because this is a private function. return super()._find_lineno( # type:ignore[misc] obj, source_lines, ) def _find(self, tests, obj, name, module, source_lines, globs, seen) -> None: if _is_mocked(obj): return with _patch_unwrap_mock_aware(): # Type ignored because this is a private function. super()._find( # type:ignore[misc] tests, obj, name, module, source_lines, globs, seen) if self.path.name == "conftest.py": if int(pytest.__version__.split(".")[0]) < 7: module = self.config.pluginmanager._importconftest( self.path, self.config.getoption("importmode"), ) else: module = self.config.pluginmanager._importconftest( self.path, self.config.getoption("importmode"), rootpath=self.config.rootpath, ) else: try: module = import_path(self.path, root=self.config.rootpath) except ImportError: if self.config.getvalue("ipdoctest_ignore_import_errors"): pytest.skip("unable to import module %r" % self.path) else: raise # Uses internal doctest module parsing mechanism. finder = MockAwareDocTestFinder(parser=IPDocTestParser()) optionflags = get_optionflags(self) runner = _get_runner( verbose=False, optionflags=optionflags, checker=_get_checker(), continue_on_failure=_get_continue_on_failure(self.config), ) for test in finder.find(module, module.__name__): if test.examples: # skip empty ipdoctests yield IPDoctestItem.from_parent(self, name=test.name, runner=runner, dtest=test)
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_messy_name(self, tmp_path: Path) -> None: # http://bitbucket.org/hpk42/py-trunk/issue/129 path = tmp_path / "foo__init__.py" path.touch() module = import_path(path) assert module.__name__ == "foo__init__"
def test_d(self, path1): otherdir = path1.join("otherdir") mod = import_path(otherdir.join("d.py")) assert mod.value2 == "got it" # type: ignore[attr-defined]
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_invalid_path(self, tmpdir): with pytest.raises(ImportError): import_path(tmpdir.join("invalid.py"))
def test_invalid_path(self, tmp_path: Path) -> None: with pytest.raises(ImportError): import_path(tmp_path / "invalid.py")
def test_importmode_importlib(self, simple_module): """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
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_importmode_twice_is_different_module(self, simple_module): """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 collect(self) -> Iterable[DoctestItem]: import doctest class MockAwareDocTestFinder(doctest.DocTestFinder): """A hackish doctest finder that overrides stdlib internals to fix a stdlib bug. https://github.com/pytest-dev/pytest/issues/3456 https://bugs.python.org/issue25532 """ def _find_lineno(self, obj, source_lines): """Doctest code does not take into account `@property`, this is a hackish way to fix it. https://bugs.python.org/issue17446 """ if isinstance(obj, property): obj = getattr(obj, "fget", obj) # Type ignored because this is a private function. return doctest.DocTestFinder._find_lineno( # type: ignore self, obj, source_lines, ) def _find( self, tests, obj, name, module, source_lines, globs, seen ) -> None: if _is_mocked(obj): return with _patch_unwrap_mock_aware(): # Type ignored because this is a private function. doctest.DocTestFinder._find( # type: ignore self, tests, obj, name, module, source_lines, globs, seen ) if self.path.name == "conftest.py": module = self.config.pluginmanager._importconftest( self.path, self.config.getoption("importmode") ) else: try: module = import_path(self.path) except ImportError: if self.config.getvalue("doctest_ignore_import_errors"): pytest.skip("unable to import module %r" % self.path) else: raise # Uses internal doctest module parsing mechanism. finder = MockAwareDocTestFinder() optionflags = get_optionflags(self) runner = _get_runner( verbose=False, optionflags=optionflags, checker=_get_checker(), continue_on_failure=_get_continue_on_failure(self.config), ) for test in finder.find(module, module.__name__): if test.examples: # skip empty doctests yield DoctestItem.from_parent( self, name=test.name, runner=runner, dtest=test )
def test_c(self, path1: Path) -> None: otherdir = path1 / "otherdir" mod = import_path(otherdir / "c.py", root=path1) assert mod.value == "got it" # type: ignore[attr-defined]