diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index eeac4684d91..686494c7e53 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,6 +12,7 @@ repos: - id: check-case-conflict - id: check-json - id: check-toml + exclude: tests/fixtures/invalid_lock/poetry\.lock - id: check-yaml - id: pretty-format-json args: [--autofix, --no-ensure-ascii, --no-sort-keys] diff --git a/src/poetry/installation/installer.py b/src/poetry/installation/installer.py index ed015952e4e..288ec2db620 100644 --- a/src/poetry/installation/installer.py +++ b/src/poetry/installation/installer.py @@ -219,7 +219,7 @@ def _do_install(self) -> int: locked_repository = Repository("poetry-locked") if self._update: - if self._locker.is_locked() and not self._lock: + if not self._lock and self._locker.is_locked(): locked_repository = self._locker.locked_repository() # If no packages have been whitelisted (The ones we want to update), diff --git a/src/poetry/packages/locker.py b/src/poetry/packages/locker.py index a3ebb3aff0a..25043027081 100644 --- a/src/poetry/packages/locker.py +++ b/src/poetry/packages/locker.py @@ -68,10 +68,7 @@ def is_locked(self) -> bool: """ Checks whether the locker has been locked (lockfile found). """ - if not self._lock.exists(): - return False - - return "package" in self.lock_data + return self._lock.exists() def is_fresh(self) -> bool: """ @@ -258,12 +255,18 @@ def set_lock_data(self, root: Package, packages: list[Package]) -> bool: "files": files, } - if not self.is_locked() or lock != self.lock_data: + do_write = True + if self.is_locked(): + try: + lock_data = self.lock_data + except RuntimeError: + # incompatible, invalid or no lock file + pass + else: + do_write = lock != lock_data + if do_write: self._write_lock_data(lock) - - return True - - return False + return do_write def _write_lock_data(self, data: TOMLDocument) -> None: self.lock.write(data) diff --git a/tests/console/commands/test_lock.py b/tests/console/commands/test_lock.py index dae977a1d27..2be58079435 100644 --- a/tests/console/commands/test_lock.py +++ b/tests/console/commands/test_lock.py @@ -67,6 +67,20 @@ def poetry_with_old_lockfile( return _project_factory("old_lock", project_factory, fixture_dir) +@pytest.fixture +def poetry_with_incompatible_lockfile( + project_factory: ProjectFactory, fixture_dir: FixtureDirGetter +) -> Poetry: + return _project_factory("incompatible_lock", project_factory, fixture_dir) + + +@pytest.fixture +def poetry_with_invalid_lockfile( + project_factory: ProjectFactory, fixture_dir: FixtureDirGetter +) -> Poetry: + return _project_factory("invalid_lock", project_factory, fixture_dir) + + def test_lock_check_outdated( command_tester_factory: CommandTesterFactory, poetry_with_outdated_lockfile: Poetry, @@ -150,3 +164,60 @@ def test_lock_no_update( for package in packages: assert locked_repository.find_packages(package.to_dependency()) + + +@pytest.mark.parametrize("is_no_update", [False, True]) +def test_lock_with_incompatible_lockfile( + command_tester_factory: CommandTesterFactory, + poetry_with_incompatible_lockfile: Poetry, + repo: TestRepository, + is_no_update: bool, +) -> None: + repo.add_package(get_package("sampleproject", "1.3.1")) + + locker = Locker( + lock=poetry_with_incompatible_lockfile.pyproject.file.path.parent + / "poetry.lock", + local_config=poetry_with_incompatible_lockfile.locker._local_config, + ) + poetry_with_incompatible_lockfile.set_locker(locker) + + tester = command_tester_factory("lock", poetry=poetry_with_incompatible_lockfile) + if is_no_update: + # not possible because of incompatible lock file + expected = ( + "(?s)lock file is not compatible .*" + " regenerate the lock file with the `poetry lock` command" + ) + with pytest.raises(RuntimeError, match=expected): + tester.execute("--no-update") + else: + # still possible because lock file is not required + status_code = tester.execute() + assert status_code == 0 + + +@pytest.mark.parametrize("is_no_update", [False, True]) +def test_lock_with_invalid_lockfile( + command_tester_factory: CommandTesterFactory, + poetry_with_invalid_lockfile: Poetry, + repo: TestRepository, + is_no_update: bool, +) -> None: + repo.add_package(get_package("sampleproject", "1.3.1")) + + locker = Locker( + lock=poetry_with_invalid_lockfile.pyproject.file.path.parent / "poetry.lock", + local_config=poetry_with_invalid_lockfile.locker._local_config, + ) + poetry_with_invalid_lockfile.set_locker(locker) + + tester = command_tester_factory("lock", poetry=poetry_with_invalid_lockfile) + if is_no_update: + # not possible because of broken lock file + with pytest.raises(RuntimeError, match="Unable to read the lock file"): + tester.execute("--no-update") + else: + # still possible because lock file is not required + status_code = tester.execute() + assert status_code == 0 diff --git a/tests/fixtures/incompatible_lock/poetry.lock b/tests/fixtures/incompatible_lock/poetry.lock new file mode 100644 index 00000000000..37615c8ce57 --- /dev/null +++ b/tests/fixtures/incompatible_lock/poetry.lock @@ -0,0 +1,2 @@ +[metadata] +lock-version = "999.0" diff --git a/tests/fixtures/incompatible_lock/pyproject.toml b/tests/fixtures/incompatible_lock/pyproject.toml new file mode 100644 index 00000000000..377aa676be9 --- /dev/null +++ b/tests/fixtures/incompatible_lock/pyproject.toml @@ -0,0 +1,15 @@ +[tool.poetry] +name = "foobar" +version = "0.1.0" +description = "" +authors = ["Poetry Developer "] + +[tool.poetry.dependencies] +python = "^3.8" +sampleproject = ">=1.3.1" + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/tests/fixtures/invalid_lock/poetry.lock b/tests/fixtures/invalid_lock/poetry.lock new file mode 100644 index 00000000000..59f4e044a6c --- /dev/null +++ b/tests/fixtures/invalid_lock/poetry.lock @@ -0,0 +1 @@ +This lock file is broken! diff --git a/tests/fixtures/invalid_lock/pyproject.toml b/tests/fixtures/invalid_lock/pyproject.toml new file mode 100644 index 00000000000..377aa676be9 --- /dev/null +++ b/tests/fixtures/invalid_lock/pyproject.toml @@ -0,0 +1,15 @@ +[tool.poetry] +name = "foobar" +version = "0.1.0" +description = "" +authors = ["Poetry Developer "] + +[tool.poetry.dependencies] +python = "^3.8" +sampleproject = ">=1.3.1" + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api"