test_editable_builder.py (poetry-1.1.15) | : | test_editable_builder.py (poetry-1.2.0) | ||
---|---|---|---|---|
# -*- coding: utf-8 -*- | from __future__ import annotations | |||
from __future__ import unicode_literals | ||||
import csv | ||||
import json | ||||
import os | import os | |||
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 deepdiff import DeepDiff | ||||
from poetry.core.semver.version import Version | ||||
from poetry.factory import Factory | from poetry.factory import Factory | |||
from poetry.io.null_io import NullIO | ||||
from poetry.masonry.builders.editable import EditableBuilder | from poetry.masonry.builders.editable import EditableBuilder | |||
from poetry.utils._compat import Path | from poetry.repositories.installed_repository import InstalledRepository | |||
from poetry.utils.env import EnvCommandError | ||||
from poetry.utils.env import EnvManager | from poetry.utils.env import EnvManager | |||
from poetry.utils.env import MockEnv | from poetry.utils.env import MockEnv | |||
from poetry.utils.env import VirtualEnv | from poetry.utils.env import VirtualEnv | |||
from poetry.utils.env import ephemeral_environment | ||||
if TYPE_CHECKING: | ||||
from pytest_mock import MockerFixture | ||||
from poetry.poetry import Poetry | ||||
@pytest.fixture() | @pytest.fixture() | |||
def simple_poetry(): | def simple_poetry() -> Poetry: | |||
poetry = Factory().create_poetry( | poetry = Factory().create_poetry( | |||
Path(__file__).parent.parent.parent / "fixtures" / "simple_project" | Path(__file__).parent.parent.parent / "fixtures" / "simple_project" | |||
) | ) | |||
return poetry | return poetry | |||
@pytest.fixture() | @pytest.fixture() | |||
def project_with_include(): | def project_with_include() -> Poetry: | |||
poetry = Factory().create_poetry( | poetry = Factory().create_poetry( | |||
Path(__file__).parent.parent.parent / "fixtures" / "with-include" | Path(__file__).parent.parent.parent / "fixtures" / "with-include" | |||
) | ) | |||
return poetry | return poetry | |||
@pytest.fixture() | @pytest.fixture() | |||
def extended_poetry(): | def extended_poetry() -> Poetry: | |||
poetry = Factory().create_poetry( | poetry = Factory().create_poetry( | |||
Path(__file__).parent.parent.parent / "fixtures" / "extended_project" | Path(__file__).parent.parent.parent / "fixtures" / "extended_project" | |||
) | ) | |||
return poetry | return poetry | |||
@pytest.fixture() | @pytest.fixture() | |||
def extended_without_setup_poetry(): | def extended_without_setup_poetry() -> Poetry: | |||
poetry = Factory().create_poetry( | poetry = Factory().create_poetry( | |||
Path(__file__).parent.parent.parent | Path(__file__).parent.parent.parent | |||
/ "fixtures" | / "fixtures" | |||
/ "extended_project_without_setup" | / "extended_project_without_setup" | |||
) | ) | |||
return poetry | return poetry | |||
@pytest.fixture() | @pytest.fixture() | |||
def env_manager(simple_poetry): | def env_manager(simple_poetry: Poetry) -> EnvManager: | |||
return EnvManager(simple_poetry) | return EnvManager(simple_poetry) | |||
@pytest.fixture | @pytest.fixture | |||
def tmp_venv(tmp_dir, env_manager): | def tmp_venv(tmp_dir: str, env_manager: EnvManager) -> VirtualEnv: | |||
venv_path = Path(tmp_dir) / "venv" | venv_path = Path(tmp_dir) / "venv" | |||
env_manager.build_venv(str(venv_path)) | env_manager.build_venv(str(venv_path)) | |||
venv = VirtualEnv(venv_path) | venv = VirtualEnv(venv_path) | |||
yield venv | yield venv | |||
shutil.rmtree(str(venv.path)) | shutil.rmtree(str(venv.path)) | |||
def test_builder_installs_proper_files_for_standard_packages(simple_poetry, tmp_ | def test_builder_installs_proper_files_for_standard_packages( | |||
venv): | simple_poetry: Poetry, tmp_venv: VirtualEnv | |||
): | ||||
builder = EditableBuilder(simple_poetry, tmp_venv, NullIO()) | builder = EditableBuilder(simple_poetry, tmp_venv, NullIO()) | |||
builder.build() | builder.build() | |||
assert tmp_venv._bin_dir.joinpath("foo").exists() | assert tmp_venv._bin_dir.joinpath("foo").exists() | |||
assert tmp_venv.site_packages.path.joinpath("simple_project.pth").exists() | pth_file = "simple_project.pth" | |||
assert simple_poetry.file.parent.resolve().as_posix() == tmp_venv.site_packa | assert tmp_venv.site_packages.exists(pth_file) | |||
ges.path.joinpath( | assert ( | |||
"simple_project.pth" | simple_poetry.file.parent.resolve().as_posix() | |||
).read_text().strip( | == tmp_venv.site_packages.find(pth_file)[0].read_text().strip(os.linesep | |||
os.linesep | ) | |||
) | ) | |||
dist_info = tmp_venv.site_packages.path.joinpath("simple_project-1.2.3.dist- | dist_info = "simple_project-1.2.3.dist-info" | |||
info") | assert tmp_venv.site_packages.exists(dist_info) | |||
assert dist_info.exists() | ||||
dist_info = tmp_venv.site_packages.find(dist_info)[0] | ||||
assert dist_info.joinpath("INSTALLER").exists() | assert dist_info.joinpath("INSTALLER").exists() | |||
assert dist_info.joinpath("METADATA").exists() | assert dist_info.joinpath("METADATA").exists() | |||
assert dist_info.joinpath("RECORD").exists() | assert dist_info.joinpath("RECORD").exists() | |||
assert dist_info.joinpath("entry_points.txt").exists() | assert dist_info.joinpath("entry_points.txt").exists() | |||
assert dist_info.joinpath("direct_url.json").exists() | ||||
assert not DeepDiff( | ||||
{ | ||||
"dir_info": {"editable": True}, | ||||
"url": simple_poetry.file.path.parent.as_uri(), | ||||
}, | ||||
json.loads(dist_info.joinpath("direct_url.json").read_text()), | ||||
) | ||||
assert "poetry" == dist_info.joinpath("INSTALLER").read_text() | assert dist_info.joinpath("INSTALLER").read_text() == "poetry" | |||
assert ( | assert ( | |||
"[console_scripts]\nbaz=bar:baz.boom.bim\nfoo=foo:bar\nfox=fuz.foo:bar.b | dist_info.joinpath("entry_points.txt").read_text() | |||
az\n\n" | == "[console_scripts]\nbaz=bar:baz.boom.bim\nfoo=foo:bar\n" | |||
== dist_info.joinpath("entry_points.txt").read_text() | "fox=fuz.foo:bar.baz\n\n" | |||
) | ) | |||
metadata = """\ | metadata = """\ | |||
Metadata-Version: 2.1 | Metadata-Version: 2.1 | |||
Name: simple-project | Name: simple-project | |||
Version: 1.2.3 | Version: 1.2.3 | |||
Summary: Some description. | Summary: Some description. | |||
Home-page: https://python-poetry.org | Home-page: https://python-poetry.org | |||
License: MIT | License: MIT | |||
Keywords: packaging,dependency,poetry | Keywords: packaging,dependency,poetry | |||
Author: Sébastien Eustace | Author: Sébastien Eustace | |||
Author-email: sebastien@eustace.io | Author-email: sebastien@eustace.io | |||
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* | Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* | |||
Classifier: License :: OSI Approved :: MIT License | Classifier: License :: OSI Approved :: MIT License | |||
Classifier: Programming Language :: Python :: 2 | Classifier: Programming Language :: Python :: 2 | |||
Classifier: Programming Language :: Python :: 2.7 | Classifier: Programming Language :: Python :: 2.7 | |||
Classifier: Programming Language :: Python :: 3 | Classifier: Programming Language :: Python :: 3 | |||
Classifier: Programming Language :: Python :: 3.10 | ||||
Classifier: Programming Language :: Python :: 3.4 | Classifier: Programming Language :: Python :: 3.4 | |||
Classifier: Programming Language :: Python :: 3.5 | Classifier: Programming Language :: Python :: 3.5 | |||
Classifier: Programming Language :: Python :: 3.6 | Classifier: Programming Language :: Python :: 3.6 | |||
Classifier: Programming Language :: Python :: 3.7 | Classifier: Programming Language :: Python :: 3.7 | |||
Classifier: Programming Language :: Python :: 3.8 | Classifier: Programming Language :: Python :: 3.8 | |||
Classifier: Programming Language :: Python :: 3.9 | Classifier: Programming Language :: Python :: 3.9 | |||
Classifier: Programming Language :: Python :: 3.10 | ||||
Classifier: Topic :: Software Development :: Build Tools | Classifier: Topic :: Software Development :: Build Tools | |||
Classifier: Topic :: Software Development :: Libraries :: Python Modules | Classifier: Topic :: Software Development :: Libraries :: Python Modules | |||
Project-URL: Documentation, https://python-poetry.org/docs | Project-URL: Documentation, https://python-poetry.org/docs | |||
Project-URL: Repository, https://github.com/python-poetry/poetry | Project-URL: Repository, https://github.com/python-poetry/poetry | |||
Description-Content-Type: text/x-rst | Description-Content-Type: text/x-rst | |||
My Package | My Package | |||
========== | ========== | |||
""" | """ | |||
assert metadata == dist_info.joinpath("METADATA").read_text(encoding="utf-8" ) | assert metadata == dist_info.joinpath("METADATA").read_text(encoding="utf-8" ) | |||
records = dist_info.joinpath("RECORD").read_text() | with open(dist_info.joinpath("RECORD"), encoding="utf-8", newline="") as f: | |||
assert str(tmp_venv.site_packages.path.joinpath("simple_project.pth")) in re | reader = csv.reader(f) | |||
cords | records = list(reader) | |||
assert str(tmp_venv._bin_dir.joinpath("foo")) in records | ||||
assert str(tmp_venv._bin_dir.joinpath("baz")) in records | assert all(len(row) == 3 for row in records) | |||
assert str(dist_info.joinpath("METADATA")) in records | record_entries = {row[0] for row in records} | |||
assert str(dist_info.joinpath("INSTALLER")) in records | pth_file = "simple_project.pth" | |||
assert str(dist_info.joinpath("entry_points.txt")) in records | assert tmp_venv.site_packages.exists(pth_file) | |||
assert str(dist_info.joinpath("RECORD")) in records | assert str(tmp_venv.site_packages.find(pth_file)[0]) in record_entries | |||
assert str(tmp_venv._bin_dir.joinpath("foo")) in record_entries | ||||
baz_script = """\ | assert str(tmp_venv._bin_dir.joinpath("baz")) in record_entries | |||
#!{python} | assert str(dist_info.joinpath("METADATA")) in record_entries | |||
assert str(dist_info.joinpath("INSTALLER")) in record_entries | ||||
assert str(dist_info.joinpath("entry_points.txt")) in record_entries | ||||
assert str(dist_info.joinpath("RECORD")) in record_entries | ||||
assert str(dist_info.joinpath("direct_url.json")) in record_entries | ||||
baz_script = f"""\ | ||||
#!{tmp_venv.python} | ||||
import sys | ||||
from bar import baz | from bar import baz | |||
if __name__ == '__main__': | if __name__ == '__main__': | |||
baz.boom.bim() | sys.exit(baz.boom.bim()) | |||
""".format( | """ | |||
python=tmp_venv._bin("python") | ||||
) | ||||
assert baz_script == tmp_venv._bin_dir.joinpath("baz").read_text() | assert baz_script == tmp_venv._bin_dir.joinpath("baz").read_text() | |||
foo_script = """\ | foo_script = f"""\ | |||
#!{python} | #!{tmp_venv.python} | |||
import sys | ||||
from foo import bar | from foo import bar | |||
if __name__ == '__main__': | if __name__ == '__main__': | |||
bar() | sys.exit(bar()) | |||
""".format( | """ | |||
python=tmp_venv._bin("python") | ||||
) | ||||
assert foo_script == tmp_venv._bin_dir.joinpath("foo").read_text() | assert foo_script == tmp_venv._bin_dir.joinpath("foo").read_text() | |||
fox_script = """\ | fox_script = f"""\ | |||
#!{python} | #!{tmp_venv.python} | |||
import sys | ||||
from fuz.foo import bar | from fuz.foo import bar | |||
if __name__ == '__main__': | if __name__ == '__main__': | |||
bar.baz() | sys.exit(bar.baz()) | |||
""".format( | """ | |||
python=tmp_venv._bin("python") | ||||
) | ||||
assert fox_script == tmp_venv._bin_dir.joinpath("fox").read_text() | assert fox_script == tmp_venv._bin_dir.joinpath("fox").read_text() | |||
def test_builder_falls_back_on_setup_and_pip_for_packages_with_build_scripts( | def test_builder_falls_back_on_setup_and_pip_for_packages_with_build_scripts( | |||
extended_poetry, | mocker: MockerFixture, extended_poetry: Poetry, tmp_dir: str | |||
): | ): | |||
env = MockEnv(path=Path("/foo")) | pip_install = mocker.patch("poetry.masonry.builders.editable.pip_install") | |||
env = MockEnv(path=Path(tmp_dir) / "foo") | ||||
builder = EditableBuilder(extended_poetry, env, NullIO()) | builder = EditableBuilder(extended_poetry, env, NullIO()) | |||
builder.build() | builder.build() | |||
pip_install.assert_called_once_with( | ||||
extended_poetry.pyproject.file.path.parent, env, upgrade=True, editable= | ||||
True | ||||
) | ||||
assert [] == env.executed | ||||
assert [ | def test_builder_setup_generation_runs_with_pip_editable(tmp_dir: str) -> None: | |||
[ | # create an isolated copy of the project | |||
"python", | fixture = Path(__file__).parent.parent.parent / "fixtures" / "extended_proje | |||
"-m", | ct" | |||
"pip", | extended_project = Path(tmp_dir) / "extended_project" | |||
"install", | ||||
"-e", | shutil.copytree(fixture, extended_project) | |||
str(extended_poetry.file.parent), | assert extended_project.exists() | |||
"--no-deps", | ||||
] | poetry = Factory().create_poetry(extended_project) | |||
] == env.executed | ||||
# we need a venv with setuptools since we are verifying setup.py builds | ||||
with ephemeral_environment(flags={"no-setuptools": False}) as venv: | ||||
builder = EditableBuilder(poetry, venv, NullIO()) | ||||
builder.build() | ||||
# is the package installed? | ||||
repository = InstalledRepository.load(venv) | ||||
package = repository.package("extended-project", Version.parse("1.2.3")) | ||||
assert package.name == "extended-project" | ||||
# check for the module built by build.py | ||||
try: | ||||
output = venv.run_python_script( | ||||
"from extended_project import built; print(built.__file__)" | ||||
).strip() | ||||
except EnvCommandError: | ||||
pytest.fail("Unable to import built module") | ||||
else: | ||||
built_py = Path(output).resolve() | ||||
expected = extended_project / "extended_project" / "built.py" | ||||
# ensure the package was installed as editable | ||||
assert built_py == expected.resolve() | ||||
def test_builder_installs_proper_files_when_packages_configured( | def test_builder_installs_proper_files_when_packages_configured( | |||
project_with_include, tmp_venv | project_with_include: Poetry, tmp_venv: VirtualEnv | |||
): | ): | |||
builder = EditableBuilder(project_with_include, tmp_venv, NullIO()) | builder = EditableBuilder(project_with_include, tmp_venv, NullIO()) | |||
builder.build() | builder.build() | |||
pth_file = tmp_venv.site_packages.path.joinpath("with_include.pth") | pth_file = "with_include.pth" | |||
assert pth_file.is_file() | assert tmp_venv.site_packages.exists(pth_file) | |||
pth_file = tmp_venv.site_packages.find(pth_file)[0] | ||||
paths = set() | paths = set() | |||
with pth_file.open() as f: | with pth_file.open() as f: | |||
for line in f.readlines(): | for line in f.readlines(): | |||
line = line.strip(os.linesep) | line = line.strip(os.linesep) | |||
if line: | if line: | |||
paths.add(line) | paths.add(line) | |||
project_root = project_with_include.file.parent.resolve() | project_root = project_with_include.file.parent.resolve() | |||
expected = {project_root.as_posix(), project_root.joinpath("src").as_posix() } | expected = {project_root.as_posix(), project_root.joinpath("src").as_posix() } | |||
assert paths.issubset(expected) | assert paths.issubset(expected) | |||
assert len(paths) == len(expected) | assert len(paths) == len(expected) | |||
def test_builder_should_execute_build_scripts(extended_without_setup_poetry): | def test_builder_should_execute_build_scripts( | |||
env = MockEnv(path=Path("/foo")) | mocker: MockerFixture, extended_without_setup_poetry: Poetry, tmp_dir: str | |||
): | ||||
env = MockEnv(path=Path(tmp_dir) / "foo") | ||||
mocker.patch( | ||||
"poetry.masonry.builders.editable.build_environment" | ||||
).return_value.__enter__.return_value = env | ||||
builder = EditableBuilder(extended_without_setup_poetry, env, NullIO()) | builder = EditableBuilder(extended_without_setup_poetry, env, NullIO()) | |||
builder.build() | builder.build() | |||
assert [ | assert [ | |||
["python", str(extended_without_setup_poetry.file.parent / "build.py")] | ["python", str(extended_without_setup_poetry.file.parent / "build.py")] | |||
] == env.executed | ] == env.executed | |||
End of changes. 34 change blocks. | ||||
72 lines changed or deleted | 140 lines changed or added |