From 075ab45ac9062c0a4e6d1e9f4340525ad83a6537 Mon Sep 17 00:00:00 2001
From: David Hotham <david.hotham@blueyonder.co.uk>
Date: Tue, 24 Jan 2023 22:39:43 +0000
Subject: [PATCH 1/3] Fix #7398: avoid infinite loop

---
 src/poetry/mixology/version_solver.py         |  4 +-
 tests/conftest.py                             | 13 ++-
 tests/console/commands/test_add.py            | 33 +++++++
 .../with_path_dependency/bazz/setup.py        | 10 ++
 .../fixtures/with_path_dependency/poetry.lock | 98 +++++++++++++++++++
 .../with_path_dependency/pyproject.toml       | 15 +++
 tests/types.py                                |  1 +
 7 files changed, 168 insertions(+), 6 deletions(-)
 create mode 100644 tests/fixtures/with_path_dependency/bazz/setup.py
 create mode 100644 tests/fixtures/with_path_dependency/poetry.lock
 create mode 100644 tests/fixtures/with_path_dependency/pyproject.toml

diff --git a/src/poetry/mixology/version_solver.py b/src/poetry/mixology/version_solver.py
index 6c66dc4c5e9..04ba762d963 100644
--- a/src/poetry/mixology/version_solver.py
+++ b/src/poetry/mixology/version_solver.py
@@ -331,7 +331,9 @@ def _resolve_conflict(self, incompatibility: Incompatibility) -> Incompatibility
             # .. _algorithm documentation:
             # https://github.com/dart-lang/pub/tree/master/doc/solver.md#conflict-resolution  # noqa: E501
             if difference is not None:
-                new_terms.append(difference.inverse)
+                inverse = difference.inverse
+                if inverse.dependency != most_recent_satisfier.dependency:
+                    new_terms.append(inverse)
 
             incompatibility = Incompatibility(
                 new_terms, ConflictCause(incompatibility, most_recent_satisfier.cause)
diff --git a/tests/conftest.py b/tests/conftest.py
index 47c11049479..5094c97b667 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -378,6 +378,7 @@ def _factory(
         install_deps: bool = True,
         source: Path | None = None,
         locker_config: dict[str, Any] | None = None,
+        use_test_locker: bool = True,
     ) -> Poetry:
         project_dir = workspace / f"poetry-fixture-{name}"
         dependencies = dependencies or {}
@@ -412,12 +413,14 @@ def _factory(
 
         poetry = Factory().create_poetry(project_dir)
 
-        locker = TestLocker(
-            poetry.locker.lock, locker_config or poetry.locker._local_config
-        )
-        locker.write()
+        if use_test_locker:
+            locker = TestLocker(
+                poetry.locker.lock, locker_config or poetry.locker._local_config
+            )
+            locker.write()
+
+            poetry.set_locker(locker)
 
-        poetry.set_locker(locker)
         poetry.set_config(config)
 
         pool = RepositoryPool()
diff --git a/tests/console/commands/test_add.py b/tests/console/commands/test_add.py
index 516934c4b32..b4dae5d452e 100644
--- a/tests/console/commands/test_add.py
+++ b/tests/console/commands/test_add.py
@@ -10,6 +10,7 @@
 from poetry.core.constraints.version import Version
 from poetry.core.packages.package import Package
 
+from poetry.puzzle.exceptions import SolverProblemError
 from poetry.repositories.legacy_repository import LegacyRepository
 from tests.helpers import get_dependency
 from tests.helpers import get_package
@@ -45,6 +46,20 @@ def poetry_with_up_to_date_lockfile(
     )
 
 
+@pytest.fixture
+def poetry_with_path_dependency(
+    project_factory: ProjectFactory, fixture_dir: FixtureDirGetter
+) -> Poetry:
+    source = fixture_dir("with_path_dependency")
+
+    poetry = project_factory(
+        name="foobar",
+        source=source,
+        use_test_locker=False,
+    )
+    return poetry
+
+
 @pytest.fixture()
 def tester(command_tester_factory: CommandTesterFactory) -> CommandTester:
     return command_tester_factory("add")
@@ -2238,3 +2253,21 @@ def error(_: Any) -> int:
     assert poetry_with_up_to_date_lockfile.file.read() == original_pyproject_content
     assert poetry_with_up_to_date_lockfile.locker.lock_data == original_lockfile_content
     assert tester.io.fetch_output() == expected
+
+
+# https://github.com/python-poetry/poetry/issues/7398
+def test_add_with_path_dependency_no_loopiness(
+    poetry_with_path_dependency: Poetry,
+    repo: TestRepository,
+    command_tester_factory: CommandTesterFactory,
+) -> None:
+    tester = command_tester_factory("add", poetry=poetry_with_path_dependency)
+
+    requests_old = get_package("requests", "2.25.1")
+    requests_new = get_package("requests", "2.28.2")
+
+    repo.add_package(requests_old)
+    repo.add_package(requests_new)
+
+    with pytest.raises(SolverProblemError):
+        tester.execute("requests")
diff --git a/tests/fixtures/with_path_dependency/bazz/setup.py b/tests/fixtures/with_path_dependency/bazz/setup.py
new file mode 100644
index 00000000000..2b81664e235
--- /dev/null
+++ b/tests/fixtures/with_path_dependency/bazz/setup.py
@@ -0,0 +1,10 @@
+from distutils.core import setup
+
+
+setup(
+    name="bazz",
+    version="1",
+    py_modules=["demo"],
+    package_dir={"src": "src"},
+    install_requires=["requests~=2.25.1"],
+)
diff --git a/tests/fixtures/with_path_dependency/poetry.lock b/tests/fixtures/with_path_dependency/poetry.lock
new file mode 100644
index 00000000000..3c2e1b9b7c6
--- /dev/null
+++ b/tests/fixtures/with_path_dependency/poetry.lock
@@ -0,0 +1,98 @@
+# This file is automatically @generated by Poetry 1.4.0.dev0 and should not be changed by hand.
+
+[[package]]
+name = "bazz"
+version = "1"
+description = ""
+category = "main"
+optional = false
+python-versions = "*"
+files = []
+develop = true
+
+[package.dependencies]
+requests = ">=2.25.1,<2.26.0"
+
+[package.source]
+type = "directory"
+url = "bazz"
+
+[[package]]
+name = "certifi"
+version = "2022.12.7"
+description = "Python package for providing Mozilla's CA Bundle."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+files = [
+    {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"},
+    {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"},
+]
+
+[[package]]
+name = "chardet"
+version = "4.0.0"
+description = "Universal encoding detector for Python 2 and 3"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+files = [
+    {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"},
+    {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"},
+]
+
+[[package]]
+name = "idna"
+version = "2.10"
+description = "Internationalized Domain Names in Applications (IDNA)"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+files = [
+    {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"},
+    {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"},
+]
+
+[[package]]
+name = "requests"
+version = "2.25.1"
+description = "Python HTTP for Humans."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+files = [
+    {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"},
+    {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"},
+]
+
+[package.dependencies]
+certifi = ">=2017.4.17"
+chardet = ">=3.0.2,<5"
+idna = ">=2.5,<3"
+urllib3 = ">=1.21.1,<1.27"
+
+[package.extras]
+security = ["cryptography (>=1.3.4)", "pyOpenSSL (>=0.14)"]
+socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
+
+[[package]]
+name = "urllib3"
+version = "1.26.14"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+files = [
+    {file = "urllib3-1.26.14-py2.py3-none-any.whl", hash = "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"},
+    {file = "urllib3-1.26.14.tar.gz", hash = "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72"},
+]
+
+[package.extras]
+brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
+secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
+socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
+
+[metadata]
+lock-version = "2.0"
+python-versions = "^3.9"
+content-hash = "81853eb3be9c7faf1950f23273d0fb3dae75833f6e4aa21b6c3038da25ba26f6"
diff --git a/tests/fixtures/with_path_dependency/pyproject.toml b/tests/fixtures/with_path_dependency/pyproject.toml
new file mode 100644
index 00000000000..705ba4fe229
--- /dev/null
+++ b/tests/fixtures/with_path_dependency/pyproject.toml
@@ -0,0 +1,15 @@
+[tool.poetry]
+name = "foobar"
+version = "0.1.0"
+description = ""
+authors = []
+readme = "README.md"
+packages = [{include = "foobar"}]
+
+[tool.poetry.dependencies]
+python = "^3.9"
+bazz = { path = "./bazz", develop = true }
+
+[build-system]
+requires = ["poetry-core"]
+build-backend = "poetry.core.masonry.api"
diff --git a/tests/types.py b/tests/types.py
index 95ce4cbc940..c990414f52e 100644
--- a/tests/types.py
+++ b/tests/types.py
@@ -46,6 +46,7 @@ def __call__(
         poetry_lock_content: str | None = None,
         install_deps: bool = True,
         source: Path | None = None,
+        use_test_locker: bool = True,
     ) -> Poetry:
         ...
 

From a3fc32116ed4bb0eeb2dbde50345b22b4a7627cd Mon Sep 17 00:00:00 2001
From: David Hotham <david.hotham@blueyonder.co.uk>
Date: Tue, 24 Jan 2023 23:05:15 +0000
Subject: [PATCH 2/3] precommit isort fix

---
 tests/fixtures/with_path_dependency/bazz/setup.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/tests/fixtures/with_path_dependency/bazz/setup.py b/tests/fixtures/with_path_dependency/bazz/setup.py
index 2b81664e235..97223196678 100644
--- a/tests/fixtures/with_path_dependency/bazz/setup.py
+++ b/tests/fixtures/with_path_dependency/bazz/setup.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 from distutils.core import setup
 
 

From 08372cc143db7f8d033b0df1f066030a4251224f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Randy=20D=C3=B6ring?=
 <30527984+radoering@users.noreply.github.com>
Date: Mon, 10 Apr 2023 13:57:09 +0200
Subject: [PATCH 3/3] docstring

---
 tests/console/commands/test_add.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/console/commands/test_add.py b/tests/console/commands/test_add.py
index b4dae5d452e..706aa637edc 100644
--- a/tests/console/commands/test_add.py
+++ b/tests/console/commands/test_add.py
@@ -2255,12 +2255,12 @@ def error(_: Any) -> int:
     assert tester.io.fetch_output() == expected
 
 
-# https://github.com/python-poetry/poetry/issues/7398
 def test_add_with_path_dependency_no_loopiness(
     poetry_with_path_dependency: Poetry,
     repo: TestRepository,
     command_tester_factory: CommandTesterFactory,
 ) -> None:
+    """https://github.com/python-poetry/poetry/issues/7398"""
     tester = command_tester_factory("add", poetry=poetry_with_path_dependency)
 
     requests_old = get_package("requests", "2.25.1")
