diff --git a/src/poetry/puzzle/provider.py b/src/poetry/puzzle/provider.py index 69302a94acb..ed88b57d977 100644 --- a/src/poetry/puzzle/provider.py +++ b/src/poetry/puzzle/provider.py @@ -50,6 +50,7 @@ from poetry.core.packages.directory_dependency import DirectoryDependency from poetry.core.packages.file_dependency import FileDependency from poetry.core.packages.package import Package + from poetry.core.packages.path_dependency import PathDependency from poetry.core.packages.url_dependency import URLDependency from poetry.core.packages.vcs_dependency import VCSDependency from poetry.core.version.markers import BaseMarker @@ -390,6 +391,7 @@ def get_package_from_vcs( ) def _search_for_file(self, dependency: FileDependency) -> Package: + dependency.validate(raise_error=True) package = self.get_package_from_file(dependency.full_path) self.validate_package_for_dependency(dependency=dependency, package=package) @@ -420,6 +422,7 @@ def get_package_from_file(cls, file_path: Path) -> Package: return package def _search_for_directory(self, dependency: DirectoryDependency) -> Package: + dependency.validate(raise_error=True) package = self.get_package_from_directory(dependency.full_path) self.validate_package_for_dependency(dependency=dependency, package=package) @@ -652,6 +655,11 @@ def complete_package( if locked is not None and locked.package.is_same_package_as(dep): continue self.search_for_direct_origin_dependency(dep) + else: + for dep in _dependencies: + if dep.is_file() or dep.is_directory(): + dep = cast("PathDependency", dep) + dep.validate(raise_error=True) dependencies = self._get_dependencies_with_overrides( _dependencies, dependency_package diff --git a/tests/console/commands/test_install.py b/tests/console/commands/test_install.py index a2bda6a4651..9e4e59d1b5e 100644 --- a/tests/console/commands/test_install.py +++ b/tests/console/commands/test_install.py @@ -14,6 +14,7 @@ from poetry.poetry import Poetry from tests.types import CommandTesterFactory + from tests.types import FixtureDirGetter from tests.types import ProjectFactory @@ -69,6 +70,22 @@ def tester( return command_tester_factory("install") +def _project_factory( + fixture_name: str, + project_factory: ProjectFactory, + fixture_dir: FixtureDirGetter, +) -> Poetry: + source = fixture_dir(fixture_name) + pyproject_content = (source / "pyproject.toml").read_text(encoding="utf-8") + poetry_lock_content = (source / "poetry.lock").read_text(encoding="utf-8") + return project_factory( + name="foobar", + pyproject_content=pyproject_content, + poetry_lock_content=poetry_lock_content, + source=source, + ) + + @pytest.mark.parametrize( ("options", "groups"), [ @@ -291,3 +308,24 @@ def test_install_logs_output_decorated(tester: CommandTester, mocker: MockerFixt ) assert tester.status_code == 0 assert tester.io.fetch_output() == expected + + +@pytest.mark.parametrize("options", ["", "--without dev"]) +@pytest.mark.parametrize( + "project", ["missing_directory_dependency", "missing_file_dependency"] +) +def test_install_path_dependency_does_not_exist( + command_tester_factory: CommandTesterFactory, + project_factory: ProjectFactory, + fixture_dir: FixtureDirGetter, + project: str, + options: str, +): + poetry = _project_factory(project, project_factory, fixture_dir) + poetry.locker.locked(True) + tester = command_tester_factory("install", poetry=poetry) + if options: + tester.execute(options) + else: + with pytest.raises(ValueError, match="does not exist"): + tester.execute(options) diff --git a/tests/console/commands/test_lock.py b/tests/console/commands/test_lock.py index a8ef7c2aac9..506f82a1776 100644 --- a/tests/console/commands/test_lock.py +++ b/tests/console/commands/test_lock.py @@ -204,6 +204,63 @@ def test_lock_no_update_path_dependencies( assert {p.name for p in packages} == {"quix", "sampleproject"} +@pytest.mark.parametrize("update", [True, False]) +@pytest.mark.parametrize( + "project", ["missing_directory_dependency", "missing_file_dependency"] +) +def test_lock_path_dependency_does_not_exist( + command_tester_factory: CommandTesterFactory, + project_factory: ProjectFactory, + fixture_dir: FixtureDirGetter, + project: str, + update: bool, +): + poetry = _project_factory(project, project_factory, fixture_dir) + locker = Locker( + lock=poetry.pyproject.file.path.parent / "poetry.lock", + local_config=poetry.locker._local_config, + ) + poetry.set_locker(locker) + options = "" if update else "--no-update" + + tester = command_tester_factory("lock", poetry=poetry) + if update or "directory" in project: + # directory dependencies are always updated + with pytest.raises(ValueError, match="does not exist"): + tester.execute(options) + else: + tester.execute(options) + + +@pytest.mark.parametrize("update", [True, False]) +@pytest.mark.parametrize( + "project", ["deleted_directory_dependency", "deleted_file_dependency"] +) +def test_lock_path_dependency_deleted_from_pyproject( + command_tester_factory: CommandTesterFactory, + project_factory: ProjectFactory, + fixture_dir: FixtureDirGetter, + project: str, + update: bool, +): + poetry = _project_factory(project, project_factory, fixture_dir) + locker = Locker( + lock=poetry.pyproject.file.path.parent / "poetry.lock", + local_config=poetry.locker._local_config, + ) + poetry.set_locker(locker) + + tester = command_tester_factory("lock", poetry=poetry) + if update: + tester.execute("") + else: + tester.execute("--no-update") + + packages = locker.locked_repository().packages + + assert {p.name for p in packages} == set() + + @pytest.mark.parametrize("is_no_update", [False, True]) def test_lock_with_incompatible_lockfile( command_tester_factory: CommandTesterFactory, diff --git a/tests/console/commands/test_show.py b/tests/console/commands/test_show.py index a927cee5bf3..8f7eadb2d3f 100644 --- a/tests/console/commands/test_show.py +++ b/tests/console/commands/test_show.py @@ -8,6 +8,7 @@ from poetry.core.packages.dependency_group import DependencyGroup from poetry.factory import Factory +from poetry.utils._compat import tomllib from tests.helpers import MOCK_DEFAULT_GIT_REVISION from tests.helpers import get_package @@ -2140,3 +2141,33 @@ def test_url_dependency_is_not_outdated_by_repository_package( # version in the repository. tester.execute("--outdated") assert tester.io.fetch_output() == "" + + +@pytest.mark.parametrize( + ("project_directory", "required_fixtures"), + [ + ( + "deleted_directory_dependency", + [], + ), + ], +) +def test_show_outdated_missing_directory_dependency( + tester: CommandTester, + poetry: Poetry, + installed: Repository, + repo: TestRepository, +): + with (poetry.pyproject.file.path.parent / "poetry.lock").open(mode="rb") as f: + data = tomllib.load(f) + poetry.locker.mock_lock_data(data) + + poetry.package.add_dependency( + Factory.create_dependency( + "missing", + {"path": data["package"][0]["source"]["url"]}, + ) + ) + + with pytest.raises(ValueError, match="does not exist"): + tester.execute("") diff --git a/tests/fixtures/deleted_directory_dependency/poetry.lock b/tests/fixtures/deleted_directory_dependency/poetry.lock new file mode 100644 index 00000000000..de0370101b8 --- /dev/null +++ b/tests/fixtures/deleted_directory_dependency/poetry.lock @@ -0,0 +1,20 @@ +# This file is automatically @generated by Poetry and should not be changed by hand. + +[[package]] +name = "missing" +version = "1.2.3" +description = "" +category = "main" +optional = false +python-versions = "*" +files = [] +develop = false + +[package.source] +type = "directory" +url = "missing" + +[metadata] +lock-version = "2.0" +python-versions = "*" +content-hash = "bec78476925e4cda6b22e91551ce4337264bdc3394c4f8297ad238f67a436d0e" diff --git a/tests/fixtures/deleted_directory_dependency/pyproject.toml b/tests/fixtures/deleted_directory_dependency/pyproject.toml new file mode 100644 index 00000000000..74151f00969 --- /dev/null +++ b/tests/fixtures/deleted_directory_dependency/pyproject.toml @@ -0,0 +1,10 @@ +[tool.poetry] +name = "project-with-missing-directory-dependency" +version = "1.2.3" +description = "This is a description" +authors = ["Your Name "] +license = "MIT" +packages = [] + +[tool.poetry.dependencies] +python = "*" diff --git a/tests/fixtures/deleted_file_dependency/poetry.lock b/tests/fixtures/deleted_file_dependency/poetry.lock new file mode 100644 index 00000000000..d5bb9f16541 --- /dev/null +++ b/tests/fixtures/deleted_file_dependency/poetry.lock @@ -0,0 +1,20 @@ +# This file is automatically @generated by Poetry and should not be changed by hand. + +[[package]] +name = "missing" +version = "1.2.3" +description = "" +category = "main" +optional = false +python-versions = "*" +files = [] +develop = false + +[package.source] +type = "file" +url = "missing-0.1.0-py2.py3-none-any.whl" + +[metadata] +lock-version = "2.0" +python-versions = "*" +content-hash = "bec78476925e4cda6b22e91551ce4337264bdc3394c4f8297ad238f67a436d0e" diff --git a/tests/fixtures/deleted_file_dependency/pyproject.toml b/tests/fixtures/deleted_file_dependency/pyproject.toml new file mode 100644 index 00000000000..74151f00969 --- /dev/null +++ b/tests/fixtures/deleted_file_dependency/pyproject.toml @@ -0,0 +1,10 @@ +[tool.poetry] +name = "project-with-missing-directory-dependency" +version = "1.2.3" +description = "This is a description" +authors = ["Your Name "] +license = "MIT" +packages = [] + +[tool.poetry.dependencies] +python = "*" diff --git a/tests/fixtures/missing_directory_dependency/poetry.lock b/tests/fixtures/missing_directory_dependency/poetry.lock new file mode 100644 index 00000000000..671a7060742 --- /dev/null +++ b/tests/fixtures/missing_directory_dependency/poetry.lock @@ -0,0 +1,20 @@ +# This file is automatically @generated by Poetry and should not be changed by hand. + +[[package]] +name = "missing" +version = "1.2.3" +description = "" +category = "dev" +optional = false +python-versions = "*" +files = [] +develop = false + +[package.source] +type = "directory" +url = "missing" + +[metadata] +lock-version = "2.0" +python-versions = "*" +content-hash = "bec78476925e4cda6b22e91551ce4337264bdc3394c4f8297ad238f67a436d0e" diff --git a/tests/fixtures/missing_directory_dependency/pyproject.toml b/tests/fixtures/missing_directory_dependency/pyproject.toml new file mode 100644 index 00000000000..570ca5debc3 --- /dev/null +++ b/tests/fixtures/missing_directory_dependency/pyproject.toml @@ -0,0 +1,13 @@ +[tool.poetry] +name = "project-with-missing-directory-dependency" +version = "1.2.3" +description = "This is a description" +authors = ["Your Name "] +license = "MIT" +packages = [] + +[tool.poetry.dependencies] +python = "*" + +[tool.poetry.dev-dependencies] +missing = { path = "./missing" } diff --git a/tests/fixtures/missing_file_dependency/poetry.lock b/tests/fixtures/missing_file_dependency/poetry.lock new file mode 100644 index 00000000000..76c3027db45 --- /dev/null +++ b/tests/fixtures/missing_file_dependency/poetry.lock @@ -0,0 +1,20 @@ +# This file is automatically @generated by Poetry and should not be changed by hand. + +[[package]] +name = "missing" +version = "1.2.3" +description = "" +category = "dev" +optional = false +python-versions = "*" +files = [] +develop = false + +[package.source] +type = "file" +url = "missing-0.1.0-py2.py3-none-any.whl" + +[metadata] +lock-version = "2.0" +python-versions = "*" +content-hash = "bec78476925e4cda6b22e91551ce4337264bdc3394c4f8297ad238f67a436d0e" diff --git a/tests/fixtures/missing_file_dependency/pyproject.toml b/tests/fixtures/missing_file_dependency/pyproject.toml new file mode 100644 index 00000000000..0be727e2104 --- /dev/null +++ b/tests/fixtures/missing_file_dependency/pyproject.toml @@ -0,0 +1,13 @@ +[tool.poetry] +name = "project-with-missing-directory-dependency" +version = "1.2.3" +description = "This is a description" +authors = ["Your Name "] +license = "MIT" +packages = [] + +[tool.poetry.dependencies] +python = "*" + +[tool.poetry.dev-dependencies] +missing = { file = "missing-0.1.0-py2.py3-none-any.whl" }