test_pip_installer.py (poetry-1.1.15) | : | test_pip_installer.py (poetry-1.2.0) | ||
---|---|---|---|---|
from __future__ import annotations | ||||
import re | ||||
import shutil | import shutil | |||
from pathlib import Path | ||||
from typing import TYPE_CHECKING | ||||
import pytest | import pytest | |||
from cleo.io.null_io import NullIO | ||||
from poetry.core.packages.package import Package | from poetry.core.packages.package import Package | |||
from poetry.installation.pip_installer import PipInstaller | from poetry.installation.pip_installer import PipInstaller | |||
from poetry.io.null_io import NullIO | ||||
from poetry.repositories.legacy_repository import LegacyRepository | from poetry.repositories.legacy_repository import LegacyRepository | |||
from poetry.repositories.pool import Pool | from poetry.repositories.pool import Pool | |||
from poetry.utils._compat import Path | from poetry.utils.authenticator import RepositoryCertificateConfig | |||
from poetry.utils.env import NullEnv | from poetry.utils.env import NullEnv | |||
if TYPE_CHECKING: | ||||
from pytest_mock import MockerFixture | ||||
from poetry.utils.env import VirtualEnv | ||||
from tests.conftest import Config | ||||
@pytest.fixture | @pytest.fixture | |||
def package_git(): | def package_git() -> Package: | |||
package = Package( | package = Package( | |||
"demo", | "demo", | |||
"1.0.0", | "1.0.0", | |||
source_type="git", | source_type="git", | |||
source_url="git@github.com:demo/demo.git", | source_url="git@github.com:demo/demo.git", | |||
source_reference="master", | source_reference="master", | |||
) | ) | |||
return package | return package | |||
@pytest.fixture | @pytest.fixture | |||
def pool(): | def package_git_with_subdirectory() -> Package: | |||
package = Package( | ||||
"subdirectories", | ||||
"2.0.0", | ||||
source_type="git", | ||||
source_url="https://github.com/demo/subdirectories.git", | ||||
source_reference="master", | ||||
source_subdirectory="two", | ||||
) | ||||
return package | ||||
@pytest.fixture | ||||
def pool() -> Pool: | ||||
return Pool() | return Pool() | |||
@pytest.fixture | @pytest.fixture | |||
def installer(pool): | def installer(pool: Pool) -> PipInstaller: | |||
return PipInstaller(NullEnv(), NullIO(), pool) | return PipInstaller(NullEnv(), NullIO(), pool) | |||
def test_requirement(installer): | def test_requirement(installer: PipInstaller): | |||
package = Package("ipython", "7.5.0") | package = Package("ipython", "7.5.0") | |||
package.files = [ | package.files = [ | |||
{"file": "foo-0.1.0.tar.gz", "hash": "md5:dbdc53e3918f28fa335a173432402a 00"}, | {"file": "foo-0.1.0.tar.gz", "hash": "md5:dbdc53e3918f28fa335a173432402a 00"}, | |||
{ | { | |||
"file": "foo.0.1.0.whl", | "file": "foo.0.1.0.whl", | |||
"hash": "e840810029224b56cd0d9e7719dc3b39cf84d577f8ac686547c8ba7a06e eab26", | "hash": "e840810029224b56cd0d9e7719dc3b39cf84d577f8ac686547c8ba7a06e eab26", | |||
}, | }, | |||
] | ] | |||
result = installer.requirement(package, formatted=True) | result = installer.requirement(package, formatted=True) | |||
expected = ( | expected = ( | |||
"ipython==7.5.0 " | "ipython==7.5.0 " | |||
"--hash md5:dbdc53e3918f28fa335a173432402a00 " | "--hash md5:dbdc53e3918f28fa335a173432402a00 " | |||
"--hash sha256:e840810029224b56cd0d9e7719dc3b39cf84d577f8ac686547c8ba7a0 6eeab26" | "--hash sha256:e840810029224b56cd0d9e7719dc3b39cf84d577f8ac686547c8ba7a0 6eeab26" | |||
"\n" | "\n" | |||
) | ) | |||
assert expected == result | assert result == expected | |||
def test_requirement_source_type_url(): | def test_requirement_source_type_url(): | |||
installer = PipInstaller(NullEnv(), NullIO(), Pool()) | installer = PipInstaller(NullEnv(), NullIO(), Pool()) | |||
foo = Package( | foo = Package( | |||
"foo", | "foo", | |||
"0.0.0", | "0.0.0", | |||
source_type="url", | source_type="url", | |||
source_url="https://somehwere.com/releases/foo-1.0.0.tar.gz", | source_url="https://somewhere.com/releases/foo-1.0.0.tar.gz", | |||
) | ) | |||
result = installer.requirement(foo, formatted=True) | result = installer.requirement(foo, formatted=True) | |||
expected = "{}#egg={}".format(foo.source_url, foo.name) | expected = f"{foo.source_url}#egg={foo.name}" | |||
assert result == expected | ||||
assert expected == result | def test_requirement_git_subdirectory( | |||
pool: Pool, package_git_with_subdirectory: Package | ||||
) -> None: | ||||
null_env = NullEnv() | ||||
installer = PipInstaller(null_env, NullIO(), pool) | ||||
result = installer.requirement(package_git_with_subdirectory) | ||||
expected = ( | ||||
"git+https://github.com/demo/subdirectories.git" | ||||
"@master#egg=subdirectories&subdirectory=two" | ||||
) | ||||
def test_requirement_git_develop_false(installer, package_git): | assert result == expected | |||
installer.install(package_git_with_subdirectory) | ||||
assert len(null_env.executed) == 1 | ||||
cmd = null_env.executed[0] | ||||
assert Path(cmd[-1]).parts[-3:] == ("demo", "subdirectories", "two") | ||||
def test_requirement_git_develop_false(installer: PipInstaller, package_git: Pac | ||||
kage): | ||||
package_git.develop = False | package_git.develop = False | |||
result = installer.requirement(package_git) | result = installer.requirement(package_git) | |||
expected = "git+git@github.com:demo/demo.git@master#egg=demo" | expected = "git+git@github.com:demo/demo.git@master#egg=demo" | |||
assert expected == result | assert result == expected | |||
def test_install_with_non_pypi_default_repository(pool, installer): | def test_install_with_non_pypi_default_repository(pool: Pool, installer: PipInst aller): | |||
default = LegacyRepository("default", "https://default.com") | default = LegacyRepository("default", "https://default.com") | |||
another = LegacyRepository("another", "https://another.com") | another = LegacyRepository("another", "https://another.com") | |||
pool.add_repository(default, default=True) | pool.add_repository(default, default=True) | |||
pool.add_repository(another) | pool.add_repository(another) | |||
foo = Package( | foo = Package( | |||
"foo", | "foo", | |||
"0.0.0", | "0.0.0", | |||
source_type="legacy", | source_type="legacy", | |||
skipping to change at line 100 | skipping to change at line 143 | |||
"bar", | "bar", | |||
"0.1.0", | "0.1.0", | |||
source_type="legacy", | source_type="legacy", | |||
source_reference=another.name, | source_reference=another.name, | |||
source_url=another.url, | source_url=another.url, | |||
) | ) | |||
installer.install(foo) | installer.install(foo) | |||
installer.install(bar) | installer.install(bar) | |||
def test_install_with_cert(): | @pytest.mark.parametrize( | |||
ca_path = "path/to/cert.pem" | ("key", "option"), | |||
pool = Pool() | [ | |||
("client_cert", "client-cert"), | ||||
default = LegacyRepository("default", "https://foo.bar", cert=Path(ca_path)) | ("cert", "cert"), | |||
], | ||||
pool.add_repository(default, default=True) | ) | |||
def test_install_with_certs(mocker: MockerFixture, key: str, option: str): | ||||
null_env = NullEnv() | ||||
installer = PipInstaller(null_env, NullIO(), pool) | ||||
foo = Package( | ||||
"foo", | ||||
"0.0.0", | ||||
source_type="legacy", | ||||
source_reference=default.name, | ||||
source_url=default.url, | ||||
) | ||||
installer.install(foo) | ||||
assert len(null_env.executed) == 1 | ||||
cmd = null_env.executed[0] | ||||
assert "--cert" in cmd | ||||
cert_index = cmd.index("--cert") | ||||
# Need to do the str(Path()) bit because Windows paths get modified by Path | ||||
assert cmd[cert_index + 1] == str(Path(ca_path)) | ||||
def test_install_with_client_cert(): | ||||
client_path = "path/to/client.pem" | client_path = "path/to/client.pem" | |||
pool = Pool() | mocker.patch( | |||
"poetry.utils.authenticator.Authenticator.get_certs_for_url", | ||||
default = LegacyRepository( | return_value=RepositoryCertificateConfig(**{key: Path(client_path)}), | |||
"default", "https://foo.bar", client_cert=Path(client_path) | ||||
) | ) | |||
default = LegacyRepository("default", "https://foo.bar") | ||||
pool = Pool() | ||||
pool.add_repository(default, default=True) | pool.add_repository(default, default=True) | |||
null_env = NullEnv() | null_env = NullEnv() | |||
installer = PipInstaller(null_env, NullIO(), pool) | installer = PipInstaller(null_env, NullIO(), pool) | |||
foo = Package( | foo = Package( | |||
"foo", | "foo", | |||
"0.0.0", | "0.0.0", | |||
source_type="legacy", | source_type="legacy", | |||
source_reference=default.name, | source_reference=default.name, | |||
source_url=default.url, | source_url=default.url, | |||
) | ) | |||
installer.install(foo) | installer.install(foo) | |||
assert len(null_env.executed) == 1 | assert len(null_env.executed) == 1 | |||
cmd = null_env.executed[0] | cmd = null_env.executed[0] | |||
assert "--client-cert" in cmd | assert f"--{option}" in cmd | |||
cert_index = cmd.index("--client-cert") | cert_index = cmd.index(f"--{option}") | |||
# Need to do the str(Path()) bit because Windows paths get modified by Path | # Need to do the str(Path()) bit because Windows paths get modified by Path | |||
assert cmd[cert_index + 1] == str(Path(client_path)) | assert cmd[cert_index + 1] == str(Path(client_path)) | |||
def test_requirement_git_develop_true(installer, package_git): | def test_requirement_git_develop_true(installer: PipInstaller, package_git: Pack age): | |||
package_git.develop = True | package_git.develop = True | |||
result = installer.requirement(package_git) | result = installer.requirement(package_git) | |||
expected = ["-e", "git+git@github.com:demo/demo.git@master#egg=demo"] | expected = ["-e", "git+git@github.com:demo/demo.git@master#egg=demo"] | |||
assert expected == result | assert result == expected | |||
def test_uninstall_git_package_nspkg_pth_cleanup(mocker, tmp_venv, pool): | 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 | # this test scenario requires a real installation using the pip installer | |||
installer = PipInstaller(tmp_venv, NullIO(), pool) | installer = PipInstaller(tmp_venv, NullIO(), pool) | |||
# use a namepspace package | # use a namespace package | |||
package = Package( | package = Package( | |||
"namespace-package-one", | "namespace-package-one", | |||
"1.0.0", | "1.0.0", | |||
source_type="git", | source_type="git", | |||
source_url="https://github.com/demo/namespace-package-one.git", | source_url="https://github.com/demo/namespace-package-one.git", | |||
source_reference="master", | source_reference="master", | |||
) | ) | |||
# we do this here because the virtual env might not be usable if failure cas | ||||
e is triggered | ||||
pth_file_candidate = tmp_venv.site_packages.path / "{}-nspkg.pth".format( | ||||
package.name | ||||
) | ||||
# in order to reproduce the scenario where the git source is removed prior t o proper | # in order to reproduce the scenario where the git source is removed prior t o proper | |||
# clean up of nspkg.pth file, we need to make sure the fixture is copied and not | # clean up of nspkg.pth file, we need to make sure the fixture is copied and not | |||
# symlinked into the git src directory | # symlinked into the git src directory | |||
def copy_only(source, dest): | def copy_only(source: Path, dest: Path) -> None: | |||
if dest.exists(): | if dest.exists(): | |||
dest.unlink() | dest.unlink() | |||
if source.is_dir(): | if source.is_dir(): | |||
shutil.copytree(str(source), str(dest)) | shutil.copytree(str(source), str(dest)) | |||
else: | else: | |||
shutil.copyfile(str(source), str(dest)) | shutil.copyfile(str(source), str(dest)) | |||
mocker.patch("tests.helpers.copy_or_symlink", new=copy_only) | mocker.patch("tests.helpers.copy_or_symlink", new=copy_only) | |||
# install package and then remove it | # install package and then remove it | |||
installer.install(package) | installer.install(package) | |||
installer.remove(package) | installer.remove(package) | |||
assert not Path(pth_file_candidate).exists() | 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 | # any command in the virtual environment should trigger the error message | |||
output = tmp_venv.run("python", "-m", "site") | output = tmp_venv.run("python", "-m", "site") | |||
assert "Error processing line 1 of {}".format(pth_file_candidate) not in out | assert not re.match(rf"Error processing line 1 of .*{pth_file}", output) | |||
put | ||||
def test_install_with_trusted_host(config: Config): | ||||
config.merge({"certificates": {"default": {"cert": False}}}) | ||||
default = LegacyRepository("default", "https://foo.bar") | ||||
pool = Pool() | ||||
pool.add_repository(default, default=True) | ||||
def test_install_directory_fallback_on_poetry_create_error(mocker, tmp_venv, poo | null_env = NullEnv() | |||
l): | ||||
import poetry.installation.pip_installer | ||||
installer = PipInstaller(null_env, NullIO(), pool) | ||||
foo = Package( | ||||
"foo", | ||||
"0.0.0", | ||||
source_type="legacy", | ||||
source_reference=default.name, | ||||
source_url=default.url, | ||||
) | ||||
installer.install(foo) | ||||
assert len(null_env.executed) == 1 | ||||
cmd = null_env.executed[0] | ||||
assert "--trusted-host" in cmd | ||||
cert_index = cmd.index("--trusted-host") | ||||
assert cmd[cert_index + 1] == "foo.bar" | ||||
def test_install_directory_fallback_on_poetry_create_error( | ||||
mocker: MockerFixture, tmp_venv: VirtualEnv, pool: Pool | ||||
): | ||||
mock_create_poetry = mocker.patch( | mock_create_poetry = mocker.patch( | |||
"poetry.factory.Factory.create_poetry", side_effect=RuntimeError | "poetry.factory.Factory.create_poetry", side_effect=RuntimeError | |||
) | ) | |||
mock_sdist_builder = mocker.patch("poetry.core.masonry.builders.sdist.SdistB uilder") | mock_sdist_builder = mocker.patch("poetry.core.masonry.builders.sdist.SdistB uilder") | |||
mock_editable_builder = mocker.patch( | mock_editable_builder = mocker.patch( | |||
"poetry.masonry.builders.editable.EditableBuilder" | "poetry.masonry.builders.editable.EditableBuilder" | |||
) | ) | |||
mock_pip_install = mocker.patch.object( | mock_pip_install = mocker.patch("poetry.installation.pip_installer.pip_insta | |||
poetry.installation.pip_installer.PipInstaller, "run" | ll") | |||
) | ||||
package = Package( | package = Package( | |||
"demo", | "demo", | |||
"1.0.0", | "1.0.0", | |||
source_type="directory", | source_type="directory", | |||
source_url=str( | source_url=str( | |||
Path(__file__).parent.parent / "fixtures/inspection/demo_poetry_pack age" | Path(__file__).parent.parent / "fixtures/inspection/demo_poetry_pack age" | |||
), | ), | |||
) | ) | |||
installer = PipInstaller(tmp_venv, NullIO(), pool) | installer = PipInstaller(tmp_venv, NullIO(), pool) | |||
installer.install_directory(package) | installer.install_directory(package) | |||
assert mock_create_poetry.call_count == 1 | assert mock_create_poetry.call_count == 1 | |||
assert mock_sdist_builder.call_count == 0 | assert mock_sdist_builder.call_count == 0 | |||
assert mock_editable_builder.call_count == 0 | assert mock_editable_builder.call_count == 0 | |||
assert mock_pip_install.call_count == 1 | assert mock_pip_install.call_count == 1 | |||
assert mock_pip_install.call_args[1].get("deps") is False | ||||
assert mock_pip_install.call_args[1].get("upgrade") is True | ||||
End of changes. 34 change blocks. | ||||
69 lines changed or deleted | 113 lines changed or added |