def test_py_info_cache_clear(mocker, tmp_path, session_app_data): spy = mocker.spy(cached_py_info, "_run_subprocess") assert PythonInfo.from_exe(sys.executable, session_app_data) is not None assert spy.call_count >= 2 # at least two, one for the venv, one more for the host PythonInfo.clear_cache(session_app_data) assert PythonInfo.from_exe(sys.executable, session_app_data) is not None assert spy.call_count >= 4
def test_bad_exe_py_info_raise(tmp_path, session_app_data): exe = str(tmp_path) with pytest.raises(RuntimeError) as context: PythonInfo.from_exe(exe, session_app_data) msg = str(context.value) assert "code" in msg assert exe in msg
def test_py_info_cached_error(mocker, tmp_path, session_app_data): spy = mocker.spy(cached_py_info, "_run_subprocess") with pytest.raises(RuntimeError): PythonInfo.from_exe(str(tmp_path), session_app_data) with pytest.raises(RuntimeError): PythonInfo.from_exe(str(tmp_path), session_app_data) assert spy.call_count == 1
def test_py_info_cached_symlink_error(mocker, tmp_path): spy = mocker.spy(cached_py_info, "_run_subprocess") with pytest.raises(RuntimeError): PythonInfo.from_exe(str(tmp_path)) symlinked = tmp_path / "a" symlinked.symlink_to(tmp_path) with pytest.raises(RuntimeError): PythonInfo.from_exe(str(symlinked)) assert spy.call_count == 2
def test_py_info_cached_symlink(mocker, tmp_path): spy = mocker.spy(cached_py_info, "_run_subprocess") first_result = PythonInfo.from_exe(sys.executable) assert first_result is not None assert spy.call_count == 2 # at least two, one for the venv, one more for the host new_exe = tmp_path / "a" new_exe.symlink_to(sys.executable) new_exe_str = str(new_exe) second_result = PythonInfo.from_exe(new_exe_str) assert second_result.executable == new_exe_str assert spy.call_count == 3 # no longer needed the host invocation, but the new symlink is must
def test_cross_major(cross_python, coverage_env, tmp_path, current_fastest, session_app_data): cmd = [ "-v", "-v", "-p", ensure_text(cross_python.executable), ensure_text(str(tmp_path)), "--no-setuptools", "--no-wheel", "--activators", "", "--creator", current_fastest, ] result = cli_run(cmd) pip_scripts = { i.name.replace(".exe", "") for i in result.creator.script_dir.iterdir() if i.name.startswith("pip") } major, minor = cross_python.version_info[0:2] assert pip_scripts == { "pip", "pip-{}.{}".format(major, minor), "pip{}".format(major) } coverage_env() env = PythonInfo.from_exe(str(result.creator.exe), session_app_data) assert env.version_info.major != CURRENT.version_info.major
def test_py_info_cached_symlink(mocker, tmp_path, session_app_data): spy = mocker.spy(cached_py_info, "_run_subprocess") first_result = PythonInfo.from_exe(sys.executable, session_app_data) assert first_result is not None count = spy.call_count # at least two, one for the venv, one more for the host exp_count = 1 if first_result.executable == sys.executable else 2 assert count >= exp_count # at least two, one for the venv, one more for the host new_exe = tmp_path / "a" new_exe.symlink_to(sys.executable) pyvenv = Path(sys.executable).parents[1] / "pyvenv.cfg" if pyvenv.exists(): (tmp_path / pyvenv.name).write_text(pyvenv.read_text()) new_exe_str = str(new_exe) second_result = PythonInfo.from_exe(new_exe_str, session_app_data) assert second_result.executable == new_exe_str assert spy.call_count == count + 1 # no longer needed the host invocation, but the new symlink is must
def test_py_info_to_system_raises(session_app_data, mocker, caplog): caplog.set_level(logging.DEBUG) mocker.patch.object(PythonInfo, "_find_possible_folders", return_value=[]) result = PythonInfo.from_exe(sys.executable, app_data=session_app_data, raise_on_error=False) assert result is None log = caplog.records[-1] assert log.levelno == logging.INFO expected = "ignore {} due cannot resolve system due to RuntimeError('failed to detect ".format(sys.executable) assert expected in log.message
def test_bad_exe_py_info_no_raise(tmp_path, caplog, capsys, session_app_data): caplog.set_level(logging.NOTSET) exe = str(tmp_path) result = PythonInfo.from_exe(exe, session_app_data, raise_on_error=False) assert result is None out, _ = capsys.readouterr() assert not out messages = [r.message for r in caplog.records if r.filename != "filelock.py"] assert len(messages) == 2 msg = messages[0] assert "get interpreter info via cmd: " in msg msg = messages[1] assert str(exe) in msg assert "code" in msg
def test_py_info_ignores_distutils_config(monkeypatch, tmp_path): (tmp_path / "setup.cfg").write_text( dedent(""" [install] prefix={0}{1}prefix install_purelib={0}{1}purelib install_platlib={0}{1}platlib install_headers={0}{1}headers install_scripts={0}{1}scripts install_data={0}{1}data """.format(tmp_path, os.sep))) monkeypatch.chdir(tmp_path) py_info = PythonInfo.from_exe(sys.executable) distutils = py_info.distutils_install for key, value in distutils.items(): assert not value.startswith(str(tmp_path)), "{}={}".format(key, value)
def test_cross_major(cross_python, coverage_env, tmp_path, current_fastest): cmd = [ "-v", "-v", "-p", ensure_text(cross_python.executable), ensure_text(str(tmp_path)), "--no-seed", "--activators", "", "--creator", current_fastest, ] result = cli_run(cmd) coverage_env() env = PythonInfo.from_exe(str(result.creator.exe)) assert env.version_info.major != CURRENT.version_info.major
def test_pyc_only(tmp_path, mocker, session_app_data): """Ensure that creation can succeed if os.pyc exists (even if os.py has been deleted)""" interpreter = PythonInfo.from_exe(sys.executable, session_app_data) host_pyc, _, host_pyc_exists = Python2.from_stdlib(Python2.mappings(interpreter), "os.pyc") if not host_pyc_exists: pytest.skip("missing system os.pyc at {}".format(host_pyc)) previous = Python2.from_stdlib def from_stdlib(mappings, name): path, to, exists = previous(mappings, name) if name.endswith(".py"): exists = False return path, to, exists mocker.patch.object(Python2, "from_stdlib", side_effect=from_stdlib) result = cli_run([ensure_text(str(tmp_path)), "--without-pip", "--activators", ""]) assert not (result.creator.stdlib / "os.py").exists() assert (result.creator.stdlib / "os.pyc").exists() assert "os.pyc" in result.creator.debug["os"]
def test_create_no_seed(python, creator, isolated, system, coverage_env, special_name_dir): dest = special_name_dir creator_key, method = creator cmd = [ "-v", "-v", "-p", ensure_text(python), ensure_text(str(dest)), "--without-pip", "--activators", "", "--creator", creator_key, "--{}".format(method), ] if isolated == "global": cmd.append("--system-site-packages") result = cli_run(cmd) creator = result.creator coverage_env() if IS_PYPY: # pypy cleans up file descriptors periodically so our (many) subprocess calls impact file descriptor limits # force a close of these on system where the limit is low-ish (e.g. MacOS 256) gc.collect() purelib = creator.purelib patch_files = { purelib / "{}.{}".format("_virtualenv", i) for i in ("py", "pyc", "pth") } patch_files.add(purelib / "__pycache__") content = set(creator.purelib.iterdir()) - patch_files assert not content, "\n".join(ensure_text(str(i)) for i in content) assert creator.env_name == ensure_text(dest.name) debug = creator.debug assert "exception" not in debug, "{}\n{}\n{}".format( debug.get("exception"), debug.get("out"), debug.get("err")) sys_path = cleanup_sys_path(debug["sys"]["path"]) system_sys_path = cleanup_sys_path(system["sys"]["path"]) our_paths = set(sys_path) - set(system_sys_path) our_paths_repr = "\n".join(ensure_text(repr(i)) for i in our_paths) # ensure we have at least one extra path added assert len(our_paths) >= 1, our_paths_repr # ensure all additional paths are related to the virtual environment for path in our_paths: msg = "\n{}\ndoes not start with {}\nhas:\n{}".format( ensure_text(str(path)), ensure_text(str(dest)), "\n".join(ensure_text(str(p)) for p in system_sys_path), ) assert str(path).startswith(str(dest)), msg # ensure there's at least a site-packages folder as part of the virtual environment added assert any(p for p in our_paths if p.parts[-1] == "site-packages"), our_paths_repr # ensure the global site package is added or not, depending on flag global_sys_path = system_sys_path[-1] if isolated == "isolated": msg = "global sys path {} is in virtual environment sys path:\n{}".format( ensure_text(str(global_sys_path)), "\n".join(ensure_text(str(j)) for j in sys_path), ) assert global_sys_path not in sys_path, msg else: common = [] for left, right in zip(reversed(system_sys_path), reversed(sys_path)): if left == right: common.append(left) else: break def list_to_str(iterable): return [ensure_text(str(i)) for i in iterable] assert common, "\n".join( difflib.unified_diff(list_to_str(sys_path), list_to_str(system_sys_path))) # test that the python executables in the bin directory are either: # - files # - absolute symlinks outside of the venv # - relative symlinks inside of the venv if sys.platform == "win32": exes = ("python.exe", ) else: exes = ("python", "python{}".format(*sys.version_info), "python{}.{}".format(*sys.version_info)) if creator_key == "venv": # for venv some repackaging does not includes the pythonx.y exes = exes[:-1] for exe in exes: exe_path = creator.bin_dir / exe assert exe_path.exists(), "\n".join( str(i) for i in creator.bin_dir.iterdir()) if not exe_path.is_symlink(): # option 1: a real file continue # it was a file link = os.readlink(str(exe_path)) if not os.path.isabs(link): # option 2: a relative symlink continue # option 3: an absolute symlink, should point outside the venv assert not link.startswith(str(creator.dest)) if IS_WIN and CURRENT.implementation == "CPython": python_w = creator.exe.parent / "pythonw.exe" assert python_w.exists() assert python_w.read_bytes() != creator.exe.read_bytes() if CPython3Posix.pyvenv_launch_patch_active( PythonInfo.from_exe(python)) and creator_key != "venv": result = subprocess.check_output( [ str(creator.exe), "-c", 'import os; print(os.environ.get("__PYVENV_LAUNCHER__"))' ], universal_newlines=True, ).strip() assert result == "None" if isinstance(creator, CPython2PosixBase): make_file = debug["makefile_filename"] assert os.path.exists(make_file) git_ignore = (dest / ".gitignore").read_text() assert git_ignore.splitlines() == [ "# created by virtualenv automatically", "*" ]