-
Notifications
You must be signed in to change notification settings - Fork 2.3k
/
test_lock.py
318 lines (251 loc) · 10 KB
/
test_lock.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
from __future__ import annotations
from pathlib import Path
from typing import TYPE_CHECKING
import pytest
from poetry.packages import Locker
from tests.helpers import get_package
if TYPE_CHECKING:
import httpretty
from cleo.testers.command_tester import CommandTester
from poetry.poetry import Poetry
from tests.helpers import TestRepository
from tests.types import CommandTesterFactory
from tests.types import FixtureDirGetter
from tests.types import ProjectFactory
@pytest.fixture
def source_dir(tmp_path: Path) -> Path:
return Path(tmp_path.as_posix())
@pytest.fixture
def tester(command_tester_factory: CommandTesterFactory) -> CommandTester:
return command_tester_factory("lock")
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.fixture
def poetry_with_outdated_lockfile(
project_factory: ProjectFactory, fixture_dir: FixtureDirGetter
) -> Poetry:
return _project_factory("outdated_lock", project_factory, fixture_dir)
@pytest.fixture
def poetry_with_up_to_date_lockfile(
project_factory: ProjectFactory, fixture_dir: FixtureDirGetter
) -> Poetry:
return _project_factory("up_to_date_lock", project_factory, fixture_dir)
@pytest.fixture
def poetry_with_old_lockfile(
project_factory: ProjectFactory, fixture_dir: FixtureDirGetter
) -> Poetry:
return _project_factory("old_lock", project_factory, fixture_dir)
@pytest.fixture
def poetry_with_nested_path_deps_old_lockfile(
project_factory: ProjectFactory, fixture_dir: FixtureDirGetter
) -> Poetry:
return _project_factory("old_lock_path_dependency", 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,
http: type[httpretty.httpretty],
):
http.disable()
locker = Locker(
lock=poetry_with_outdated_lockfile.pyproject.file.path.parent / "poetry.lock",
local_config=poetry_with_outdated_lockfile.locker._local_config,
)
poetry_with_outdated_lockfile.set_locker(locker)
tester = command_tester_factory("lock", poetry=poetry_with_outdated_lockfile)
status_code = tester.execute("--check")
expected = (
"Error: poetry.lock is not consistent with pyproject.toml. "
"Run `poetry lock [--no-update]` to fix it.\n"
)
assert tester.io.fetch_error() == expected
# exit with an error
assert status_code == 1
def test_lock_check_up_to_date(
command_tester_factory: CommandTesterFactory,
poetry_with_up_to_date_lockfile: Poetry,
http: type[httpretty.httpretty],
):
http.disable()
locker = Locker(
lock=poetry_with_up_to_date_lockfile.pyproject.file.path.parent / "poetry.lock",
local_config=poetry_with_up_to_date_lockfile.locker._local_config,
)
poetry_with_up_to_date_lockfile.set_locker(locker)
tester = command_tester_factory("lock", poetry=poetry_with_up_to_date_lockfile)
status_code = tester.execute("--check")
expected = "poetry.lock is consistent with pyproject.toml.\n"
assert tester.io.fetch_output() == expected
# exit with an error
assert status_code == 0
def test_lock_no_update(
command_tester_factory: CommandTesterFactory,
poetry_with_old_lockfile: Poetry,
repo: TestRepository,
):
repo.add_package(get_package("sampleproject", "1.3.1"))
repo.add_package(get_package("sampleproject", "2.0.0"))
locker = Locker(
lock=poetry_with_old_lockfile.pyproject.file.path.parent / "poetry.lock",
local_config=poetry_with_old_lockfile.locker._local_config,
)
poetry_with_old_lockfile.set_locker(locker)
locked_repository = poetry_with_old_lockfile.locker.locked_repository()
assert (
poetry_with_old_lockfile.locker.lock_data["metadata"].get("lock-version")
== "1.0"
)
tester = command_tester_factory("lock", poetry=poetry_with_old_lockfile)
tester.execute("--no-update")
locker = Locker(
lock=poetry_with_old_lockfile.pyproject.file.path.parent / "poetry.lock",
local_config={},
)
packages = locker.locked_repository().packages
assert len(packages) == len(locked_repository.packages)
assert locker.lock_data["metadata"].get("lock-version") == "2.0"
for package in packages:
assert locked_repository.find_packages(package.to_dependency())
def test_lock_no_update_path_dependencies(
command_tester_factory: CommandTesterFactory,
poetry_with_nested_path_deps_old_lockfile: Poetry,
repo: TestRepository,
):
"""
The lock file contains a variant of the directory dependency "quix" that does
not depend on "sampleproject". Although the version of "quix" has not been changed,
it should be re-solved because there is always only one valid version
of a directory dependency at any time.
"""
repo.add_package(get_package("sampleproject", "1.3.1"))
locker = Locker(
lock=poetry_with_nested_path_deps_old_lockfile.pyproject.file.path.parent
/ "poetry.lock",
local_config=poetry_with_nested_path_deps_old_lockfile.locker._local_config,
)
poetry_with_nested_path_deps_old_lockfile.set_locker(locker)
tester = command_tester_factory(
"lock", poetry=poetry_with_nested_path_deps_old_lockfile
)
tester.execute("--no-update")
packages = locker.locked_repository().packages
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,
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