def test_env_finds_the_correct_executables_for_generic_env( tmp_dir: str, manager: EnvManager ): venv_path = Path(tmp_dir) / "Virtual Env" child_venv_path = Path(tmp_dir) / "Child Virtual Env" manager.build_venv(str(venv_path), with_pip=True) parent_venv = VirtualEnv(venv_path) manager.build_venv( str(child_venv_path), executable=parent_venv.python, with_pip=True ) venv = GenericEnv(parent_venv.path, child_env=VirtualEnv(child_venv_path)) expected_executable = ( f"python{sys.version_info[0]}.{sys.version_info[1]}{'.exe' if WINDOWS else ''}" ) expected_pip_executable = ( f"pip{sys.version_info[0]}.{sys.version_info[1]}{'.exe' if WINDOWS else ''}" ) if WINDOWS: expected_executable = "python.exe" expected_pip_executable = "pip.exe" assert Path(venv.python).name == expected_executable assert Path(venv.pip).name == expected_pip_executable
def test_env_commands_with_spaces_in_their_arg_work_as_expected( tmp_dir, manager): venv_path = Path(tmp_dir) / "Virtual Env" manager.build_venv(str(venv_path)) venv = VirtualEnv(venv_path) assert venv.run("python", venv.pip, "--version", shell=True).startswith( "pip {} from ".format(venv.pip_version))
def test_env_finds_the_correct_executables(tmp_dir, manager): venv_path = Path(tmp_dir) / "Virtual Env" manager.build_venv(str(venv_path), with_pip=True) venv = VirtualEnv(venv_path) default_executable = expected_executable = "python" + (".exe" if WINDOWS else "") default_pip_executable = expected_pip_executable = "pip" + ( ".exe" if WINDOWS else "") major_executable = "python{}{}".format(sys.version_info[0], ".exe" if WINDOWS else "") major_pip_executable = "pip{}{}".format(sys.version_info[0], ".exe" if WINDOWS else "") if (venv._bin_dir.joinpath(default_executable).exists() and venv._bin_dir.joinpath(major_executable).exists()): venv._bin_dir.joinpath(default_executable).unlink() expected_executable = major_executable if (venv._bin_dir.joinpath(default_pip_executable).exists() and venv._bin_dir.joinpath(major_pip_executable).exists()): venv._bin_dir.joinpath(default_pip_executable).unlink() expected_pip_executable = major_pip_executable venv = VirtualEnv(venv_path) assert Path(venv.python).name == expected_executable assert Path(venv.pip).name.startswith( expected_pip_executable.split(".")[0])
def test_env_shell_commands_with_stdinput_in_their_arg_work_as_expected( tmp_dir, manager): venv_path = Path(tmp_dir) / "Virtual Env" manager.build_venv(str(venv_path)) venv = VirtualEnv(venv_path) assert venv.run("python", "-", input_=GET_BASE_PREFIX, shell=True).strip() == str(venv.get_base_prefix())
def test_run_with_keyboard_interrupt( tmp_dir: str, tmp_venv: VirtualEnv, mocker: "MockerFixture" ): mocker.patch("subprocess.run", side_effect=KeyboardInterrupt()) with pytest.raises(KeyboardInterrupt): tmp_venv.run("python", "-", input_=MINIMAL_SCRIPT) subprocess.run.assert_called_once()
def test_run_with_called_process_error(tmp_dir: str, tmp_venv: VirtualEnv, mocker: MockerFixture): mocker.patch("subprocess.run", side_effect=subprocess.CalledProcessError(42, "some_command")) with pytest.raises(EnvCommandError): tmp_venv.run("python", "-", input_=MINIMAL_SCRIPT) subprocess.run.assert_called_once()
def test_run_with_input_non_zero_return(tmp_dir: str, tmp_venv: VirtualEnv): with pytest.raises(EnvCommandError) as process_error: # Test command that will return non-zero returncode. tmp_venv.run("python", "-", input_=ERRORING_SCRIPT) assert process_error.value.e.returncode == 1
def test_virtualenvs_with_spaces_in_their_path_work_as_expected(tmp_dir): venv_path = Path(tmp_dir) / "Virtual Env" Env.build_venv(str(venv_path)) venv = VirtualEnv(venv_path) assert venv.run("python", "-V", shell=True).startswith("Python")
def test_call_no_input_with_keyboard_interrupt( tmp_dir: str, tmp_venv: VirtualEnv, mocker: "MockerFixture" ): mocker.patch("subprocess.call", side_effect=KeyboardInterrupt()) kwargs = {"call": True} with pytest.raises(KeyboardInterrupt): tmp_venv.run("python", "-", **kwargs) subprocess.call.assert_called_once()
def test_call_with_input_and_keyboard_interrupt(tmp_dir: str, tmp_venv: VirtualEnv, mocker: MockerFixture): mocker.patch("subprocess.run", side_effect=KeyboardInterrupt()) kwargs = {"call": True} with pytest.raises(KeyboardInterrupt): tmp_venv.run("python", "-", input_=MINIMAL_SCRIPT, **kwargs) subprocess.run.assert_called_once()
def test_env_get_supported_tags_matches_inside_virtualenv( tmp_dir: str, manager: EnvManager): venv_path = Path(tmp_dir) / "Virtual Env" manager.build_venv(str(venv_path)) venv = VirtualEnv(venv_path) import packaging.tags assert venv.get_supported_tags() == list(packaging.tags.sys_tags())
def test_call_no_input_with_called_process_error(tmp_dir: str, tmp_venv: VirtualEnv, mocker: MockerFixture): mocker.patch("subprocess.call", side_effect=subprocess.CalledProcessError(42, "some_command")) kwargs = {"call": True} with pytest.raises(EnvCommandError): tmp_venv.run("python", "-", **kwargs) subprocess.call.assert_called_once()
def test_env_finds_fallback_executables_for_generic_env(tmp_dir, manager): venv_path = Path(tmp_dir) / "Virtual Env" child_venv_path = Path(tmp_dir) / "Child Virtual Env" manager.build_venv(str(venv_path), with_pip=True) parent_venv = VirtualEnv(venv_path) manager.build_venv(str(child_venv_path), executable=parent_venv.python, with_pip=True) venv = GenericEnv(parent_venv.path, child_env=VirtualEnv(child_venv_path)) default_executable = "python" + (".exe" if WINDOWS else "") major_executable = "python{}{}".format(sys.version_info[0], ".exe" if WINDOWS else "") minor_executable = "python{}.{}{}".format(sys.version_info[0], sys.version_info[1], ".exe" if WINDOWS else "") expected_executable = minor_executable if (venv._bin_dir.joinpath(expected_executable).exists() and venv._bin_dir.joinpath(major_executable).exists()): venv._bin_dir.joinpath(expected_executable).unlink() expected_executable = major_executable if (venv._bin_dir.joinpath(expected_executable).exists() and venv._bin_dir.joinpath(default_executable).exists()): venv._bin_dir.joinpath(expected_executable).unlink() expected_executable = default_executable default_pip_executable = "pip" + (".exe" if WINDOWS else "") major_pip_executable = "pip{}{}".format(sys.version_info[0], ".exe" if WINDOWS else "") minor_pip_executable = "pip{}.{}{}".format(sys.version_info[0], sys.version_info[1], ".exe" if WINDOWS else "") expected_pip_executable = minor_pip_executable if (venv._bin_dir.joinpath(expected_pip_executable).exists() and venv._bin_dir.joinpath(major_pip_executable).exists()): venv._bin_dir.joinpath(expected_pip_executable).unlink() expected_pip_executable = major_pip_executable if (venv._bin_dir.joinpath(expected_pip_executable).exists() and venv._bin_dir.joinpath(default_pip_executable).exists()): venv._bin_dir.joinpath(expected_pip_executable).unlink() expected_pip_executable = default_pip_executable if not venv._bin_dir.joinpath(expected_executable).exists(): expected_executable = default_executable if not venv._bin_dir.joinpath(expected_pip_executable).exists(): expected_pip_executable = default_pip_executable venv = GenericEnv(parent_venv.path, child_env=VirtualEnv(child_venv_path)) assert Path(venv.python).name == expected_executable assert Path(venv.pip).name == expected_pip_executable
def _sane_env(poetry, build_dir, io, clean=False): "Yield a sane virtual environment in build_dir." manager = EnvManager(poetry) if os.path.isdir(build_dir): env = VirtualEnv(build_dir) if clean or (not env.is_sane()): io.write_line(f"removing env {build_dir}") manager.remove_venv(build_dir) if not os.path.isdir(build_dir): io.write_line(f"building env {build_dir}") manager.build_venv(build_dir, executable=None) return VirtualEnv(build_dir)
def _pep517_metadata(cls, path): # type (Path) -> PackageInfo """ Helper method to use PEP-517 library to build and read package metadata. :param path: Path to package source to build and read metadata for. """ info = None try: info = cls.from_setup_files(path) if info.requires_dist is not None: return info except PackageInfoError: pass with temporary_directory() as tmp_dir: # TODO: cache PEP 517 build environment corresponding to each project venv venv_dir = Path(tmp_dir) / ".venv" EnvManager.build_venv(venv_dir.as_posix()) venv = VirtualEnv(venv_dir, venv_dir) dest_dir = Path(tmp_dir) / "dist" dest_dir.mkdir() try: venv.run("python", "-m", "pip", "install", "--disable-pip-version-check", "--ignore-installed", *PEP517_META_BUILD_DEPS) venv.run( "python", "-", input_=PEP517_META_BUILD.format(source=path.as_posix(), dest=dest_dir.as_posix()), ) return cls.from_metadata(dest_dir) except EnvCommandError as e: # something went wrong while attempting pep517 metadata build # fallback to egg_info if setup.py available cls._log("PEP517 build failed: {}".format(e), level="debug") setup_py = path / "setup.py" if not setup_py.exists(): raise PackageInfoError(path) cwd = Path.cwd() os.chdir(path.as_posix()) try: venv.run("python", "setup.py", "egg_info") return cls.from_metadata(path) except EnvCommandError: raise PackageInfoError(path) finally: os.chdir(cwd.as_posix()) if info: cls._log( "Falling back to parsed setup.py file for {}".format(path), "debug") return info # if we reach here, everything has failed and all hope is lost raise PackageInfoError(path)
def test_env_site_packages_should_find_the_site_packages_directory_if_root(tmp_dir): site_packages = Path(tmp_dir).joinpath("site-packages") site_packages.mkdir(parents=True) env = VirtualEnv(Path(tmp_dir), Path(tmp_dir)) assert site_packages == env.site_packages
def tmp_venv(tmp_dir, config, request): venv_path = Path(tmp_dir) / "venv" EnvManager(config).build_venv(str(venv_path)) venv = VirtualEnv(venv_path) yield venv shutil.rmtree(str(venv.path))
def tmp_venv(tmp_dir: str) -> Iterator[VirtualEnv]: venv_path = Path(tmp_dir) / "venv" EnvManager.build_venv(str(venv_path)) venv = VirtualEnv(venv_path) yield venv shutil.rmtree(str(venv.path))
def tmp_venv(tmp_dir, env_manager): venv_path = Path(tmp_dir) / "venv" env_manager.build_venv(str(venv_path)) venv = VirtualEnv(venv_path) yield venv shutil.rmtree(str(venv.path))
def test_env_finds_the_correct_executables_for_generic_env(tmp_dir, manager): venv_path = Path(tmp_dir) / "Virtual Env" child_venv_path = Path(tmp_dir) / "Child Virtual Env" manager.build_venv(str(venv_path)) parent_venv = VirtualEnv(venv_path) manager.build_venv(str(child_venv_path), executable=parent_venv.python) venv = GenericEnv(parent_venv.path, child_env=VirtualEnv(child_venv_path)) expected_executable = "python{}.{}{}".format(sys.version_info[0], sys.version_info[1], ".exe" if WINDOWS else "") expected_pip_executable = "pip{}.{}{}".format(sys.version_info[0], sys.version_info[1], ".exe" if WINDOWS else "") if WINDOWS: expected_executable = "python.exe" expected_pip_executable = "pip.exe" assert Path(venv.python).name == expected_executable assert Path(venv.pip).name == expected_pip_executable
def test_env_site_packages_should_find_the_site_packages_directory_if_standard( tmp_dir): if WINDOWS: site_packages = Path(tmp_dir).joinpath("Lib/site-packages") else: site_packages = Path(tmp_dir).joinpath( "lib/python{}/site-packages".format(".".join( str(v) for v in sys.version_info[:2]))) site_packages.mkdir(parents=True) env = VirtualEnv(Path(tmp_dir), Path(tmp_dir)) assert site_packages == env.site_packages
def test_env_no_pip(tmp_path: Path, poetry: Poetry, flags: dict[str, bool], packages: set[str]): venv_path = tmp_path / "venv" EnvManager(poetry).build_venv(path=venv_path, flags=flags) env = VirtualEnv(venv_path) installed_repository = InstalledRepository.load(env=env, with_dependencies=True) installed_packages = { package.name for package in installed_repository.packages # workaround for BSD test environments if package.name != "sqlite3" } assert installed_packages == packages
def test_env_has_symlinks_on_nix(tmp_dir): venv_path = Path(tmp_dir) / "Virtual Env" Env.build_venv(str(venv_path)) venv = VirtualEnv(venv_path) venv_available = False try: from venv import EnvBuilder venv_available = True except ImportError: pass if os.name != "nt" and venv_available: assert os.path.islink(venv.python)
def activate(self, env: VirtualEnv) -> int | None: activate_script = self._get_activate_script() bin_dir = "Scripts" if WINDOWS else "bin" activate_path = env.path / bin_dir / activate_script # mypy requires using sys.platform instead of WINDOWS constant # in if statements to properly type check on Windows if sys.platform == "win32": if self._name in ("powershell", "pwsh"): args = ["-NoExit", "-File", str(activate_path)] else: # /K will execute the bat file and # keep the cmd process from terminating args = ["/K", str(activate_path)] completed_proc = subprocess.run([self.path, *args]) return completed_proc.returncode import shlex terminal = Terminal() with env.temp_environ(): c = pexpect.spawn(self._path, ["-i"], dimensions=(terminal.height, terminal.width)) if self._name == "zsh": c.setecho(False) c.sendline( f"{self._get_source_command()} {shlex.quote(str(activate_path))}") def resize(sig: Any, data: Any) -> None: terminal = Terminal() c.setwinsize(terminal.height, terminal.width) signal.signal(signal.SIGWINCH, resize) # Interact with the new shell. c.interact(escape_character=None) c.close() sys.exit(c.exitstatus)
def test_uninstall_git_package_nspkg_pth_cleanup( mocker: MockerFixture, tmp_venv: VirtualEnv, pool: Pool ): # this test scenario requires a real installation using the pip installer installer = PipInstaller(tmp_venv, NullIO(), pool) # use a namepspace package package = Package( "namespace-package-one", "1.0.0", source_type="git", source_url="https://github.com/demo/namespace-package-one.git", source_reference="master", ) # in order to reproduce the scenario where the git source is removed prior to proper # clean up of nspkg.pth file, we need to make sure the fixture is copied and not # symlinked into the git src directory def copy_only(source: Path, dest: Path) -> None: if dest.exists(): dest.unlink() if source.is_dir(): shutil.copytree(str(source), str(dest)) else: shutil.copyfile(str(source), str(dest)) mocker.patch("tests.helpers.copy_or_symlink", new=copy_only) # install package and then remove it installer.install(package) installer.remove(package) pth_file = f"{package.name}-nspkg.pth" assert not tmp_venv.site_packages.exists(pth_file) # any command in the virtual environment should trigger the error message output = tmp_venv.run("python", "-m", "site") assert not re.match(rf"Error processing line 1 of .*{pth_file}", output)
def _execute_setup(self): with temporary_directory() as tmp_dir: EnvManager.build_venv(tmp_dir) venv = VirtualEnv(Path(tmp_dir), Path(tmp_dir)) venv.run("python", "setup.py", "egg_info")
def get_package_from_directory( cls, directory, name=None ): # type: (Path, Optional[str]) -> Package supports_poetry = False pyproject = directory.joinpath("pyproject.toml") if pyproject.exists(): pyproject = TomlFile(pyproject) pyproject_content = pyproject.read() supports_poetry = ( "tool" in pyproject_content and "poetry" in pyproject_content["tool"] ) if supports_poetry: poetry = Factory().create_poetry(directory) pkg = poetry.package package = Package(pkg.name, pkg.version) for dep in pkg.requires: if not dep.is_optional(): package.requires.append(dep) for extra, deps in pkg.extras.items(): if extra not in package.extras: package.extras[extra] = [] for dep in deps: package.extras[extra].append(dep) package.python_versions = pkg.python_versions else: # Execute egg_info current_dir = os.getcwd() os.chdir(str(directory)) try: with temporary_directory() as tmp_dir: EnvManager.build_venv(tmp_dir) venv = VirtualEnv(Path(tmp_dir), Path(tmp_dir)) venv.run("python", "setup.py", "egg_info") except EnvCommandError: result = SetupReader.read_from_directory(directory) if not result["name"]: # The name could not be determined # We use the dependency name result["name"] = name if not result["version"]: # The version could not be determined # so we raise an error since it is mandatory raise RuntimeError( "Unable to retrieve the package version for {}".format( directory ) ) package_name = result["name"] package_version = result["version"] python_requires = result["python_requires"] if python_requires is None: python_requires = "*" package_summary = "" requires = "" for dep in result["install_requires"]: requires += dep + "\n" if result["extras_require"]: requires += "\n" for extra_name, deps in result["extras_require"].items(): requires += "[{}]\n".format(extra_name) for dep in deps: requires += dep + "\n" requires += "\n" reqs = parse_requires(requires) else: os.chdir(current_dir) # Sometimes pathlib will fail on recursive # symbolic links, so we need to workaround it # and use the glob module instead. # Note that this does not happen with pathlib2 # so it's safe to use it for Python < 3.4. if PY35: egg_info = next( Path(p) for p in glob.glob( os.path.join(str(directory), "**", "*.egg-info"), recursive=True, ) ) else: egg_info = next(directory.glob("**/*.egg-info")) meta = pkginfo.UnpackedSDist(str(egg_info)) package_name = meta.name package_version = meta.version package_summary = meta.summary python_requires = meta.requires_python if meta.requires_dist: reqs = list(meta.requires_dist) else: reqs = [] requires = egg_info / "requires.txt" if requires.exists(): with requires.open(encoding="utf-8") as f: reqs = parse_requires(f.read()) finally: os.chdir(current_dir) package = Package(package_name, package_version) package.description = package_summary for req in reqs: dep = dependency_from_pep_508(req) if dep.in_extras: for extra in dep.in_extras: if extra not in package.extras: package.extras[extra] = [] package.extras[extra].append(dep) if not dep.is_optional(): package.requires.append(dep) if python_requires: package.python_versions = python_requires if name and name != package.name: # For now, the dependency's name must match the actual package's name raise RuntimeError( "The dependency name for {} does not match the actual package's name: {}".format( name, package.name ) ) package.source_type = "directory" package.source_url = directory.as_posix() return package
def test_run_with_input(tmp_dir: str, tmp_venv: VirtualEnv): result = tmp_venv.run("python", "-", input_=MINIMAL_SCRIPT) assert result == "Minimal Output" + os.linesep