1import json
2import os
3import sys
4import textwrap
5
6import pytest
7from pip._vendor.packaging.utils import canonicalize_name
8
9from tests.lib import (
10    create_basic_sdist_for_package,
11    create_basic_wheel_for_package,
12    create_test_package_with_setup,
13)
14from tests.lib.wheel import make_wheel
15
16
17def assert_installed(script, **kwargs):
18    ret = script.pip('list', '--format=json')
19    installed = set(
20        (canonicalize_name(val['name']), val['version'])
21        for val in json.loads(ret.stdout)
22    )
23    expected = set((canonicalize_name(k), v) for k, v in kwargs.items())
24    assert expected <= installed, \
25        "{!r} not all in {!r}".format(expected, installed)
26
27
28def assert_not_installed(script, *args):
29    ret = script.pip("list", "--format=json")
30    installed = set(
31        canonicalize_name(val["name"])
32        for val in json.loads(ret.stdout)
33    )
34    # None of the given names should be listed as installed, i.e. their
35    # intersection should be empty.
36    expected = set(canonicalize_name(k) for k in args)
37    assert not (expected & installed), \
38        "{!r} contained in {!r}".format(expected, installed)
39
40
41def assert_editable(script, *args):
42    # This simply checks whether all of the listed packages have a
43    # corresponding .egg-link file installed.
44    # TODO: Implement a more rigorous way to test for editable installations.
45    egg_links = set("{}.egg-link".format(arg) for arg in args)
46    assert egg_links <= set(os.listdir(script.site_packages_path)), \
47        "{!r} not all found in {!r}".format(args, script.site_packages_path)
48
49
50def test_new_resolver_can_install(script):
51    create_basic_wheel_for_package(
52        script,
53        "simple",
54        "0.1.0",
55    )
56    script.pip(
57        "install",
58        "--no-cache-dir", "--no-index",
59        "--find-links", script.scratch_path,
60        "simple"
61    )
62    assert_installed(script, simple="0.1.0")
63
64
65def test_new_resolver_can_install_with_version(script):
66    create_basic_wheel_for_package(
67        script,
68        "simple",
69        "0.1.0",
70    )
71    script.pip(
72        "install",
73        "--no-cache-dir", "--no-index",
74        "--find-links", script.scratch_path,
75        "simple==0.1.0"
76    )
77    assert_installed(script, simple="0.1.0")
78
79
80def test_new_resolver_picks_latest_version(script):
81    create_basic_wheel_for_package(
82        script,
83        "simple",
84        "0.1.0",
85    )
86    create_basic_wheel_for_package(
87        script,
88        "simple",
89        "0.2.0",
90    )
91    script.pip(
92        "install",
93        "--no-cache-dir", "--no-index",
94        "--find-links", script.scratch_path,
95        "simple"
96    )
97    assert_installed(script, simple="0.2.0")
98
99
100def test_new_resolver_picks_installed_version(script):
101    create_basic_wheel_for_package(
102        script,
103        "simple",
104        "0.1.0",
105    )
106    create_basic_wheel_for_package(
107        script,
108        "simple",
109        "0.2.0",
110    )
111    script.pip(
112        "install",
113        "--no-cache-dir", "--no-index",
114        "--find-links", script.scratch_path,
115        "simple==0.1.0"
116    )
117    assert_installed(script, simple="0.1.0")
118
119    result = script.pip(
120        "install",
121        "--no-cache-dir", "--no-index",
122        "--find-links", script.scratch_path,
123        "simple"
124    )
125    assert "Collecting" not in result.stdout, "Should not fetch new version"
126    assert_installed(script, simple="0.1.0")
127
128
129def test_new_resolver_picks_installed_version_if_no_match_found(script):
130    create_basic_wheel_for_package(
131        script,
132        "simple",
133        "0.1.0",
134    )
135    create_basic_wheel_for_package(
136        script,
137        "simple",
138        "0.2.0",
139    )
140    script.pip(
141        "install",
142        "--no-cache-dir", "--no-index",
143        "--find-links", script.scratch_path,
144        "simple==0.1.0"
145    )
146    assert_installed(script, simple="0.1.0")
147
148    result = script.pip(
149        "install",
150        "--no-cache-dir", "--no-index",
151        "simple"
152    )
153    assert "Collecting" not in result.stdout, "Should not fetch new version"
154    assert_installed(script, simple="0.1.0")
155
156
157def test_new_resolver_installs_dependencies(script):
158    create_basic_wheel_for_package(
159        script,
160        "base",
161        "0.1.0",
162        depends=["dep"],
163    )
164    create_basic_wheel_for_package(
165        script,
166        "dep",
167        "0.1.0",
168    )
169    script.pip(
170        "install",
171        "--no-cache-dir", "--no-index",
172        "--find-links", script.scratch_path,
173        "base"
174    )
175    assert_installed(script, base="0.1.0", dep="0.1.0")
176
177
178def test_new_resolver_ignore_dependencies(script):
179    create_basic_wheel_for_package(
180        script,
181        "base",
182        "0.1.0",
183        depends=["dep"],
184    )
185    create_basic_wheel_for_package(
186        script,
187        "dep",
188        "0.1.0",
189    )
190    script.pip(
191        "install",
192        "--no-cache-dir", "--no-index", "--no-deps",
193        "--find-links", script.scratch_path,
194        "base"
195    )
196    assert_installed(script, base="0.1.0")
197    assert_not_installed(script, "dep")
198
199
200@pytest.mark.parametrize(
201    "root_dep",
202    [
203        "base[add]",
204        "base[add] >= 0.1.0",
205    ],
206)
207def test_new_resolver_installs_extras(tmpdir, script, root_dep):
208    req_file = tmpdir.joinpath("requirements.txt")
209    req_file.write_text(root_dep)
210
211    create_basic_wheel_for_package(
212        script,
213        "base",
214        "0.1.0",
215        extras={"add": ["dep"]},
216    )
217    create_basic_wheel_for_package(
218        script,
219        "dep",
220        "0.1.0",
221    )
222    script.pip(
223        "install",
224        "--no-cache-dir", "--no-index",
225        "--find-links", script.scratch_path,
226        "-r", req_file,
227    )
228    assert_installed(script, base="0.1.0", dep="0.1.0")
229
230
231def test_new_resolver_installs_extras_deprecated(tmpdir, script):
232    req_file = tmpdir.joinpath("requirements.txt")
233    req_file.write_text("base >= 0.1.0[add]")
234
235    create_basic_wheel_for_package(
236        script,
237        "base",
238        "0.1.0",
239        extras={"add": ["dep"]},
240    )
241    create_basic_wheel_for_package(
242        script,
243        "dep",
244        "0.1.0",
245    )
246    result = script.pip(
247        "install",
248        "--no-cache-dir", "--no-index",
249        "--find-links", script.scratch_path,
250        "-r", req_file,
251        expect_stderr=True
252    )
253    assert "DEPRECATION: Extras after version" in result.stderr
254    assert_installed(script, base="0.1.0", dep="0.1.0")
255
256
257def test_new_resolver_installs_extras_warn_missing(script):
258    create_basic_wheel_for_package(
259        script,
260        "base",
261        "0.1.0",
262        extras={"add": ["dep"]},
263    )
264    create_basic_wheel_for_package(
265        script,
266        "dep",
267        "0.1.0",
268    )
269    result = script.pip(
270        "install",
271        "--no-cache-dir", "--no-index",
272        "--find-links", script.scratch_path,
273        "base[add,missing]",
274        expect_stderr=True,
275    )
276    assert "does not provide the extra" in result.stderr, str(result)
277    assert "missing" in result.stderr, str(result)
278    assert_installed(script, base="0.1.0", dep="0.1.0")
279
280
281def test_new_resolver_installed_message(script):
282    create_basic_wheel_for_package(script, "A", "1.0")
283    result = script.pip(
284        "install",
285        "--no-cache-dir", "--no-index",
286        "--find-links", script.scratch_path,
287        "A",
288        expect_stderr=False,
289    )
290    assert "Successfully installed A-1.0" in result.stdout, str(result)
291
292
293def test_new_resolver_no_dist_message(script):
294    create_basic_wheel_for_package(script, "A", "1.0")
295    result = script.pip(
296        "install",
297        "--no-cache-dir", "--no-index",
298        "--find-links", script.scratch_path,
299        "B",
300        expect_error=True,
301        expect_stderr=True,
302    )
303
304    # Full messages from old resolver:
305    # ERROR: Could not find a version that satisfies the
306    #        requirement xxx (from versions: none)
307    # ERROR: No matching distribution found for xxx
308
309    assert "Could not find a version that satisfies the requirement B" \
310        in result.stderr, str(result)
311    assert "No matching distribution found for B" in result.stderr, str(result)
312
313
314def test_new_resolver_installs_editable(script):
315    create_basic_wheel_for_package(
316        script,
317        "base",
318        "0.1.0",
319        depends=["dep"],
320    )
321    source_dir = create_test_package_with_setup(
322        script,
323        name="dep",
324        version="0.1.0",
325    )
326    script.pip(
327        "install",
328        "--no-cache-dir", "--no-index",
329        "--find-links", script.scratch_path,
330        "base",
331        "--editable", source_dir,
332    )
333    assert_installed(script, base="0.1.0", dep="0.1.0")
334    assert_editable(script, "dep")
335
336
337@pytest.mark.parametrize(
338    "requires_python, ignore_requires_python, dep_version",
339    [
340        # Something impossible to satisfy.
341        ("<2", False, "0.1.0"),
342        ("<2", True, "0.2.0"),
343
344        # Something guaranteed to satisfy.
345        (">=2", False, "0.2.0"),
346        (">=2", True, "0.2.0"),
347    ],
348)
349def test_new_resolver_requires_python(
350    script,
351    requires_python,
352    ignore_requires_python,
353    dep_version,
354):
355    create_basic_wheel_for_package(
356        script,
357        "base",
358        "0.1.0",
359        depends=["dep"],
360    )
361    create_basic_wheel_for_package(
362        script,
363        "dep",
364        "0.1.0",
365    )
366    create_basic_wheel_for_package(
367        script,
368        "dep",
369        "0.2.0",
370        requires_python=requires_python,
371    )
372
373    args = [
374        "install",
375        "--no-cache-dir",
376        "--no-index",
377        "--find-links", script.scratch_path,
378    ]
379    if ignore_requires_python:
380        args.append("--ignore-requires-python")
381    args.append("base")
382
383    script.pip(*args)
384
385    assert_installed(script, base="0.1.0", dep=dep_version)
386
387
388def test_new_resolver_requires_python_error(script):
389    create_basic_wheel_for_package(
390        script,
391        "base",
392        "0.1.0",
393        requires_python="<2",
394    )
395    result = script.pip(
396        "install",
397        "--no-cache-dir", "--no-index",
398        "--find-links", script.scratch_path,
399        "base",
400        expect_error=True,
401    )
402
403    message = (
404        "Package 'base' requires a different Python: "
405        "{}.{}.{} not in '<2'".format(*sys.version_info[:3])
406    )
407    assert message in result.stderr, str(result)
408
409
410def test_new_resolver_installed(script):
411    create_basic_wheel_for_package(
412        script,
413        "base",
414        "0.1.0",
415        depends=["dep"],
416    )
417    create_basic_wheel_for_package(
418        script,
419        "dep",
420        "0.1.0",
421    )
422
423    result = script.pip(
424        "install",
425        "--no-cache-dir", "--no-index",
426        "--find-links", script.scratch_path,
427        "base",
428    )
429    assert "Requirement already satisfied" not in result.stdout, str(result)
430
431    result = script.pip(
432        "install",
433        "--no-cache-dir", "--no-index",
434        "--find-links", script.scratch_path,
435        "base~=0.1.0",
436    )
437    assert "Requirement already satisfied: base~=0.1.0" in result.stdout, \
438        str(result)
439    result.did_not_update(
440        script.site_packages / "base",
441        message="base 0.1.0 reinstalled"
442    )
443
444
445def test_new_resolver_ignore_installed(script):
446    create_basic_wheel_for_package(
447        script,
448        "base",
449        "0.1.0",
450    )
451    satisfied_output = "Requirement already satisfied"
452
453    result = script.pip(
454        "install",
455        "--no-cache-dir", "--no-index",
456        "--find-links", script.scratch_path,
457        "base",
458    )
459    assert satisfied_output not in result.stdout, str(result)
460
461    result = script.pip(
462        "install",
463        "--no-cache-dir", "--no-index", "--ignore-installed",
464        "--find-links", script.scratch_path,
465        "base",
466    )
467    assert satisfied_output not in result.stdout, str(result)
468    result.did_update(
469        script.site_packages / "base",
470        message="base 0.1.0 not reinstalled"
471    )
472
473
474def test_new_resolver_only_builds_sdists_when_needed(script):
475    create_basic_wheel_for_package(
476        script,
477        "base",
478        "0.1.0",
479        depends=["dep"],
480    )
481    create_basic_sdist_for_package(
482        script,
483        "dep",
484        "0.1.0",
485        # Replace setup.py with something that fails
486        extra_files={"setup.py": "assert False"},
487    )
488    create_basic_sdist_for_package(
489        script,
490        "dep",
491        "0.2.0",
492    )
493    # We only ever need to check dep 0.2.0 as it's the latest version
494    script.pip(
495        "install",
496        "--no-cache-dir", "--no-index",
497        "--find-links", script.scratch_path,
498        "base"
499    )
500    assert_installed(script, base="0.1.0", dep="0.2.0")
501
502    # We merge criteria here, as we have two "dep" requirements
503    script.pip(
504        "install",
505        "--no-cache-dir", "--no-index",
506        "--find-links", script.scratch_path,
507        "base", "dep"
508    )
509    assert_installed(script, base="0.1.0", dep="0.2.0")
510
511
512def test_new_resolver_install_different_version(script):
513    create_basic_wheel_for_package(script, "base", "0.1.0")
514    create_basic_wheel_for_package(script, "base", "0.2.0")
515
516    script.pip(
517        "install",
518        "--no-cache-dir", "--no-index",
519        "--find-links", script.scratch_path,
520        "base==0.1.0",
521    )
522
523    # This should trigger an uninstallation of base.
524    result = script.pip(
525        "install",
526        "--no-cache-dir", "--no-index",
527        "--find-links", script.scratch_path,
528        "base==0.2.0",
529    )
530
531    assert "Uninstalling base-0.1.0" in result.stdout, str(result)
532    assert "Successfully uninstalled base-0.1.0" in result.stdout, str(result)
533    result.did_update(
534        script.site_packages / "base",
535        message="base not upgraded"
536    )
537    assert_installed(script, base="0.2.0")
538
539
540def test_new_resolver_force_reinstall(script):
541    create_basic_wheel_for_package(script, "base", "0.1.0")
542
543    script.pip(
544        "install",
545        "--no-cache-dir", "--no-index",
546        "--find-links", script.scratch_path,
547        "base==0.1.0",
548    )
549
550    # This should trigger an uninstallation of base due to --force-reinstall,
551    # even though the installed version matches.
552    result = script.pip(
553        "install",
554        "--no-cache-dir", "--no-index",
555        "--find-links", script.scratch_path,
556        "--force-reinstall",
557        "base==0.1.0",
558    )
559
560    assert "Uninstalling base-0.1.0" in result.stdout, str(result)
561    assert "Successfully uninstalled base-0.1.0" in result.stdout, str(result)
562    result.did_update(
563        script.site_packages / "base",
564        message="base not reinstalled"
565    )
566    assert_installed(script, base="0.1.0")
567
568
569@pytest.mark.parametrize(
570    "available_versions, pip_args, expected_version",
571    [
572        # Choose the latest non-prerelease by default.
573        (["1.0", "2.0a1"], ["pkg"], "1.0"),
574        # Choose the prerelease if the specifier spells out a prerelease.
575        (["1.0", "2.0a1"], ["pkg==2.0a1"], "2.0a1"),
576        # Choose the prerelease if explicitly allowed by the user.
577        (["1.0", "2.0a1"], ["pkg", "--pre"], "2.0a1"),
578        # Choose the prerelease if no stable releases are available.
579        (["2.0a1"], ["pkg"], "2.0a1"),
580    ],
581    ids=["default", "exact-pre", "explicit-pre", "no-stable"],
582)
583def test_new_resolver_handles_prerelease(
584    script,
585    available_versions,
586    pip_args,
587    expected_version,
588):
589    for version in available_versions:
590        create_basic_wheel_for_package(script, "pkg", version)
591    script.pip(
592        "install",
593        "--no-cache-dir", "--no-index",
594        "--find-links", script.scratch_path,
595        *pip_args
596    )
597    assert_installed(script, pkg=expected_version)
598
599
600@pytest.mark.parametrize(
601    "pkg_deps, root_deps",
602    [
603        # This tests the marker is picked up from a transitive dependency.
604        (["dep; os_name == 'nonexist_os'"], ["pkg"]),
605        # This tests the marker is picked up from a root dependency.
606        ([], ["pkg", "dep; os_name == 'nonexist_os'"]),
607    ]
608)
609def test_new_reolver_skips_marker(script, pkg_deps, root_deps):
610    create_basic_wheel_for_package(script, "pkg", "1.0", depends=pkg_deps)
611    create_basic_wheel_for_package(script, "dep", "1.0")
612
613    script.pip(
614        "install",
615        "--no-cache-dir", "--no-index",
616        "--find-links", script.scratch_path,
617        *root_deps
618    )
619    assert_installed(script, pkg="1.0")
620    assert_not_installed(script, "dep")
621
622
623@pytest.mark.parametrize(
624    "constraints",
625    [
626        ["pkg<2.0", "constraint_only<1.0"],
627        # This also tests the pkg constraint don't get merged with the
628        # requirement prematurely. (pypa/pip#8134)
629        ["pkg<2.0"],
630    ]
631)
632def test_new_resolver_constraints(script, constraints):
633    create_basic_wheel_for_package(script, "pkg", "1.0")
634    create_basic_wheel_for_package(script, "pkg", "2.0")
635    create_basic_wheel_for_package(script, "pkg", "3.0")
636    constraints_file = script.scratch_path / "constraints.txt"
637    constraints_file.write_text("\n".join(constraints))
638    script.pip(
639        "install",
640        "--no-cache-dir", "--no-index",
641        "--find-links", script.scratch_path,
642        "-c", constraints_file,
643        "pkg"
644    )
645    assert_installed(script, pkg="1.0")
646    assert_not_installed(script, "constraint_only")
647
648
649def test_new_resolver_constraint_no_specifier(script):
650    "It's allowed (but useless...) for a constraint to have no specifier"
651    create_basic_wheel_for_package(script, "pkg", "1.0")
652    constraints_file = script.scratch_path / "constraints.txt"
653    constraints_file.write_text("pkg")
654    script.pip(
655        "install",
656        "--no-cache-dir", "--no-index",
657        "--find-links", script.scratch_path,
658        "-c", constraints_file,
659        "pkg"
660    )
661    assert_installed(script, pkg="1.0")
662
663
664@pytest.mark.parametrize(
665    "constraint, error",
666    [
667        (
668            "dist.zip",
669            "Unnamed requirements are not allowed as constraints",
670        ),
671        (
672            "req @ https://example.com/dist.zip",
673            "Links are not allowed as constraints",
674        ),
675        (
676            "pkg[extra]",
677            "Constraints cannot have extras",
678        ),
679    ],
680)
681def test_new_resolver_constraint_reject_invalid(script, constraint, error):
682    create_basic_wheel_for_package(script, "pkg", "1.0")
683    constraints_file = script.scratch_path / "constraints.txt"
684    constraints_file.write_text(constraint)
685    result = script.pip(
686        "install",
687        "--no-cache-dir", "--no-index",
688        "--find-links", script.scratch_path,
689        "-c", constraints_file,
690        "pkg",
691        expect_error=True,
692        expect_stderr=True,
693    )
694    assert error in result.stderr, str(result)
695
696
697def test_new_resolver_constraint_on_dependency(script):
698    create_basic_wheel_for_package(script, "base", "1.0", depends=["dep"])
699    create_basic_wheel_for_package(script, "dep", "1.0")
700    create_basic_wheel_for_package(script, "dep", "2.0")
701    create_basic_wheel_for_package(script, "dep", "3.0")
702    constraints_file = script.scratch_path / "constraints.txt"
703    constraints_file.write_text("dep==2.0")
704    script.pip(
705        "install",
706        "--no-cache-dir", "--no-index",
707        "--find-links", script.scratch_path,
708        "-c", constraints_file,
709        "base"
710    )
711    assert_installed(script, base="1.0")
712    assert_installed(script, dep="2.0")
713
714
715@pytest.mark.parametrize(
716    "constraint_version, expect_error, message",
717    [
718        ("1.0", True, "ERROR: No matching distribution found for foo 2.0"),
719        ("2.0", False, "Successfully installed foo-2.0"),
720    ],
721)
722def test_new_resolver_constraint_on_path_empty(
723    script,
724    constraint_version,
725    expect_error,
726    message,
727):
728    """A path requirement can be filtered by a constraint.
729    """
730    setup_py = script.scratch_path / "setup.py"
731    text = "from setuptools import setup\nsetup(name='foo', version='2.0')"
732    setup_py.write_text(text)
733
734    constraints_txt = script.scratch_path / "constraints.txt"
735    constraints_txt.write_text("foo=={}".format(constraint_version))
736
737    result = script.pip(
738        "install",
739        "--no-cache-dir", "--no-index",
740        "-c", constraints_txt,
741        str(script.scratch_path),
742        expect_error=expect_error,
743    )
744
745    if expect_error:
746        assert message in result.stderr, str(result)
747    else:
748        assert message in result.stdout, str(result)
749
750
751def test_new_resolver_constraint_only_marker_match(script):
752    create_basic_wheel_for_package(script, "pkg", "1.0")
753    create_basic_wheel_for_package(script, "pkg", "2.0")
754    create_basic_wheel_for_package(script, "pkg", "3.0")
755
756    constrants_content = textwrap.dedent(
757        """
758        pkg==1.0; python_version == "{ver[0]}.{ver[1]}"  # Always satisfies.
759        pkg==2.0; python_version < "0"  # Never satisfies.
760        """
761    ).format(ver=sys.version_info)
762    constraints_txt = script.scratch_path / "constraints.txt"
763    constraints_txt.write_text(constrants_content)
764
765    script.pip(
766        "install",
767        "--no-cache-dir", "--no-index",
768        "-c", constraints_txt,
769        "--find-links", script.scratch_path,
770        "pkg",
771    )
772    assert_installed(script, pkg="1.0")
773
774
775def test_new_resolver_upgrade_needs_option(script):
776    # Install pkg 1.0.0
777    create_basic_wheel_for_package(script, "pkg", "1.0.0")
778    script.pip(
779        "install",
780        "--no-cache-dir", "--no-index",
781        "--find-links", script.scratch_path,
782        "pkg",
783    )
784
785    # Now release a new version
786    create_basic_wheel_for_package(script, "pkg", "2.0.0")
787
788    # This should not upgrade because we don't specify --upgrade
789    result = script.pip(
790        "install",
791        "--no-cache-dir", "--no-index",
792        "--find-links", script.scratch_path,
793        "pkg",
794    )
795
796    assert "Requirement already satisfied" in result.stdout, str(result)
797    assert_installed(script, pkg="1.0.0")
798
799    # This should upgrade
800    result = script.pip(
801        "install",
802        "--no-cache-dir", "--no-index",
803        "--find-links", script.scratch_path,
804        "--upgrade",
805        "PKG",  # Deliberately uppercase to check canonicalization
806    )
807
808    assert "Uninstalling pkg-1.0.0" in result.stdout, str(result)
809    assert "Successfully uninstalled pkg-1.0.0" in result.stdout, str(result)
810    result.did_update(
811        script.site_packages / "pkg",
812        message="pkg not upgraded"
813    )
814    assert_installed(script, pkg="2.0.0")
815
816
817def test_new_resolver_upgrade_strategy(script):
818    create_basic_wheel_for_package(script, "base", "1.0.0", depends=["dep"])
819    create_basic_wheel_for_package(script, "dep", "1.0.0")
820    script.pip(
821        "install",
822        "--no-cache-dir", "--no-index",
823        "--find-links", script.scratch_path,
824        "base",
825    )
826
827    assert_installed(script, base="1.0.0")
828    assert_installed(script, dep="1.0.0")
829
830    # Now release new versions
831    create_basic_wheel_for_package(script, "base", "2.0.0", depends=["dep"])
832    create_basic_wheel_for_package(script, "dep", "2.0.0")
833
834    script.pip(
835        "install",
836        "--no-cache-dir", "--no-index",
837        "--find-links", script.scratch_path,
838        "--upgrade",
839        "base",
840    )
841
842    # With upgrade strategy "only-if-needed" (the default), dep should not
843    # be upgraded.
844    assert_installed(script, base="2.0.0")
845    assert_installed(script, dep="1.0.0")
846
847    create_basic_wheel_for_package(script, "base", "3.0.0", depends=["dep"])
848    script.pip(
849        "install",
850        "--no-cache-dir", "--no-index",
851        "--find-links", script.scratch_path,
852        "--upgrade", "--upgrade-strategy=eager",
853        "base",
854    )
855
856    # With upgrade strategy "eager", dep should be upgraded.
857    assert_installed(script, base="3.0.0")
858    assert_installed(script, dep="2.0.0")
859
860
861class TestExtraMerge(object):
862    """
863    Test installing a package that depends the same package with different
864    extras, one listed as required and the other as in extra.
865    """
866
867    def _local_with_setup(script, name, version, requires, extras):
868        """Create the package as a local source directory to install from path.
869        """
870        return create_test_package_with_setup(
871            script,
872            name=name,
873            version=version,
874            install_requires=requires,
875            extras_require=extras,
876        )
877
878    def _direct_wheel(script, name, version, requires, extras):
879        """Create the package as a wheel to install from path directly.
880        """
881        return create_basic_wheel_for_package(
882            script,
883            name=name,
884            version=version,
885            depends=requires,
886            extras=extras,
887        )
888
889    def _wheel_from_index(script, name, version, requires, extras):
890        """Create the package as a wheel to install from index.
891        """
892        create_basic_wheel_for_package(
893            script,
894            name=name,
895            version=version,
896            depends=requires,
897            extras=extras,
898        )
899        return name
900
901    @pytest.mark.parametrize(
902        "pkg_builder",
903        [
904            _local_with_setup,
905            _direct_wheel,
906            _wheel_from_index,
907        ],
908    )
909    def test_new_resolver_extra_merge_in_package(
910        self, monkeypatch, script, pkg_builder,
911    ):
912        create_basic_wheel_for_package(script, "depdev", "1.0.0")
913        create_basic_wheel_for_package(
914            script,
915            "dep",
916            "1.0.0",
917            extras={"dev": ["depdev"]},
918        )
919        requirement = pkg_builder(
920            script,
921            name="pkg",
922            version="1.0.0",
923            requires=["dep"],
924            extras={"dev": ["dep[dev]"]},
925        )
926
927        script.pip(
928            "install",
929            "--no-cache-dir", "--no-index",
930            "--find-links", script.scratch_path,
931            requirement + "[dev]",
932        )
933        assert_installed(script, pkg="1.0.0", dep="1.0.0", depdev="1.0.0")
934
935
936def test_new_resolver_build_directory_error_zazo_19(script):
937    """https://github.com/pradyunsg/zazo/issues/19#issuecomment-631615674
938
939    This will first resolve like this:
940
941    1. Pin pkg-b==2.0.0 (since pkg-b has fewer choices)
942    2. Pin pkg-a==3.0.0 -> Conflict due to dependency pkg-b<2
943    3. Pin pkg-b==1.0.0
944
945    Since pkg-b is only available as sdist, both the first and third steps
946    would trigger building from source. This ensures the preparer can build
947    different versions of a package for the resolver.
948
949    The preparer would fail with the following message if the different
950    versions end up using the same build directory::
951
952        ERROR: pip can't proceed with requirements 'pkg-b ...' due to a
953        pre-existing build directory (...). This is likely due to a previous
954        installation that failed. pip is being responsible and not assuming it
955        can delete this. Please delete it and try again.
956    """
957    create_basic_wheel_for_package(
958        script, "pkg_a", "3.0.0", depends=["pkg-b<2"],
959    )
960    create_basic_wheel_for_package(script, "pkg_a", "2.0.0")
961    create_basic_wheel_for_package(script, "pkg_a", "1.0.0")
962
963    create_basic_sdist_for_package(script, "pkg_b", "2.0.0")
964    create_basic_sdist_for_package(script, "pkg_b", "1.0.0")
965
966    script.pip(
967        "install",
968        "--no-cache-dir", "--no-index",
969        "--find-links", script.scratch_path,
970        "pkg-a", "pkg-b",
971    )
972    assert_installed(script, pkg_a="3.0.0", pkg_b="1.0.0")
973
974
975def test_new_resolver_upgrade_same_version(script):
976    create_basic_wheel_for_package(script, "pkg", "2")
977    create_basic_wheel_for_package(script, "pkg", "1")
978
979    script.pip(
980        "install",
981        "--no-cache-dir", "--no-index",
982        "--find-links", script.scratch_path,
983        "pkg",
984    )
985    assert_installed(script, pkg="2")
986
987    script.pip(
988        "install",
989        "--no-cache-dir", "--no-index",
990        "--find-links", script.scratch_path,
991        "--upgrade",
992        "pkg",
993    )
994    assert_installed(script, pkg="2")
995
996
997def test_new_resolver_local_and_req(script):
998    source_dir = create_test_package_with_setup(
999        script,
1000        name="pkg",
1001        version="0.1.0",
1002    )
1003    script.pip(
1004        "install",
1005        "--no-cache-dir", "--no-index",
1006        source_dir, "pkg!=0.1.0",
1007        expect_error=True,
1008    )
1009
1010
1011def test_new_resolver_no_deps_checks_requires_python(script):
1012    create_basic_wheel_for_package(
1013        script,
1014        "base",
1015        "0.1.0",
1016        depends=["dep"],
1017        requires_python="<2",  # Something that always fails.
1018    )
1019    create_basic_wheel_for_package(
1020        script,
1021        "dep",
1022        "0.2.0",
1023    )
1024
1025    result = script.pip(
1026        "install",
1027        "--no-cache-dir",
1028        "--no-index",
1029        "--no-deps",
1030        "--find-links", script.scratch_path,
1031        "base",
1032        expect_error=True,
1033    )
1034
1035    message = (
1036        "Package 'base' requires a different Python: "
1037        "{}.{}.{} not in '<2'".format(*sys.version_info[:3])
1038    )
1039    assert message in result.stderr
1040
1041
1042def test_new_resolver_prefers_installed_in_upgrade_if_latest(script):
1043    create_basic_wheel_for_package(script, "pkg", "1")
1044    local_pkg = create_test_package_with_setup(script, name="pkg", version="2")
1045
1046    # Install the version that's not on the index.
1047    script.pip(
1048        "install",
1049        "--no-cache-dir",
1050        "--no-index",
1051        local_pkg,
1052    )
1053
1054    # Now --upgrade should still pick the local version because it's "better".
1055    script.pip(
1056        "install",
1057        "--no-cache-dir",
1058        "--no-index",
1059        "--find-links", script.scratch_path,
1060        "--upgrade",
1061        "pkg",
1062    )
1063    assert_installed(script, pkg="2")
1064
1065
1066@pytest.mark.parametrize("N", [2, 10, 20])
1067def test_new_resolver_presents_messages_when_backtracking_a_lot(script, N):
1068    # Generate a set of wheels that will definitely cause backtracking.
1069    for index in range(1, N+1):
1070        A_version = "{index}.0.0".format(index=index)
1071        B_version = "{index}.0.0".format(index=index)
1072        C_version = "{index_minus_one}.0.0".format(index_minus_one=index - 1)
1073
1074        depends = ["B == " + B_version]
1075        if index != 1:
1076            depends.append("C == " + C_version)
1077
1078        print("A", A_version, "B", B_version, "C", C_version)
1079        create_basic_wheel_for_package(script, "A", A_version, depends=depends)
1080
1081    for index in range(1, N+1):
1082        B_version = "{index}.0.0".format(index=index)
1083        C_version = "{index}.0.0".format(index=index)
1084        depends = ["C == " + C_version]
1085
1086        print("B", B_version, "C", C_version)
1087        create_basic_wheel_for_package(script, "B", B_version, depends=depends)
1088
1089    for index in range(1, N+1):
1090        C_version = "{index}.0.0".format(index=index)
1091        print("C", C_version)
1092        create_basic_wheel_for_package(script, "C", C_version)
1093
1094    # Install A
1095    result = script.pip(
1096        "install",
1097        "--no-cache-dir",
1098        "--no-index",
1099        "--find-links", script.scratch_path,
1100        "A"
1101    )
1102
1103    assert_installed(script, A="1.0.0", B="1.0.0", C="1.0.0")
1104    # These numbers are hard-coded in the code.
1105    if N >= 1:
1106        assert "This could take a while." in result.stdout
1107    if N >= 8:
1108        assert result.stdout.count("This could take a while.") >= 2
1109    if N >= 13:
1110        assert "press Ctrl + C" in result.stdout
1111
1112
1113@pytest.mark.parametrize(
1114    "metadata_version",
1115    [
1116        "0.1.0+local.1",  # Normalized form.
1117        "0.1.0+local_1",  # Non-normalized form containing an underscore.
1118
1119        # Non-normalized form containing a dash. This is allowed, installation
1120        # works correctly, but assert_installed() fails because pkg_resources
1121        # cannot handle it correctly. Nobody is complaining about it right now,
1122        # we're probably dropping it for importlib.metadata soon(tm), so let's
1123        # ignore it for the time being.
1124        pytest.param("0.1.0+local-1", marks=pytest.mark.xfail),
1125    ],
1126    ids=["meta_dot", "meta_underscore", "meta_dash"],
1127)
1128@pytest.mark.parametrize(
1129    "filename_version",
1130    [
1131        ("0.1.0+local.1"),  # Tools are encouraged to use this.
1132        ("0.1.0+local_1"),  # But this is allowed (version not normalized).
1133    ],
1134    ids=["file_dot", "file_underscore"],
1135)
1136def test_new_resolver_check_wheel_version_normalized(
1137    script,
1138    metadata_version,
1139    filename_version,
1140):
1141    filename = "simple-{}-py2.py3-none-any.whl".format(filename_version)
1142
1143    wheel_builder = make_wheel(name="simple", version=metadata_version)
1144    wheel_builder.save_to(script.scratch_path / filename)
1145
1146    script.pip(
1147        "install",
1148        "--no-cache-dir", "--no-index",
1149        "--find-links", script.scratch_path,
1150        "simple"
1151    )
1152    assert_installed(script, simple="0.1.0+local.1")
1153
1154
1155def test_new_resolver_does_reinstall_local_sdists(script):
1156    archive_path = create_basic_sdist_for_package(
1157        script,
1158        "pkg",
1159        "1.0",
1160    )
1161    script.pip(
1162        "install", "--no-cache-dir", "--no-index",
1163        archive_path,
1164    )
1165    assert_installed(script, pkg="1.0")
1166
1167    result = script.pip(
1168        "install", "--no-cache-dir", "--no-index",
1169        archive_path,
1170        expect_stderr=True,
1171    )
1172    assert "Installing collected packages: pkg" in result.stdout, str(result)
1173    assert "DEPRECATION" in result.stderr, str(result)
1174    assert_installed(script, pkg="1.0")
1175
1176
1177def test_new_resolver_does_reinstall_local_paths(script):
1178    pkg = create_test_package_with_setup(
1179        script,
1180        name="pkg",
1181        version="1.0"
1182    )
1183    script.pip(
1184        "install", "--no-cache-dir", "--no-index",
1185        pkg,
1186    )
1187    assert_installed(script, pkg="1.0")
1188
1189    result = script.pip(
1190        "install", "--no-cache-dir", "--no-index",
1191        pkg,
1192    )
1193    assert "Installing collected packages: pkg" in result.stdout, str(result)
1194    assert_installed(script, pkg="1.0")
1195
1196
1197def test_new_resolver_does_not_reinstall_when_from_a_local_index(script):
1198    create_basic_sdist_for_package(
1199        script,
1200        "simple",
1201        "0.1.0",
1202    )
1203    script.pip(
1204        "install",
1205        "--no-cache-dir", "--no-index",
1206        "--find-links", script.scratch_path,
1207        "simple"
1208    )
1209    assert_installed(script, simple="0.1.0")
1210
1211    result = script.pip(
1212        "install",
1213        "--no-cache-dir", "--no-index",
1214        "--find-links", script.scratch_path,
1215        "simple"
1216    )
1217    # Should not reinstall!
1218    assert "Installing collected packages: simple" not in result.stdout, str(result)
1219    assert "Requirement already satisfied: simple" in result.stdout, str(result)
1220    assert_installed(script, simple="0.1.0")
1221
1222
1223def test_new_resolver_skip_inconsistent_metadata(script):
1224    create_basic_wheel_for_package(script, "A", "1")
1225
1226    a_2 = create_basic_wheel_for_package(script, "A", "2")
1227    a_2.rename(a_2.parent.joinpath("a-3-py2.py3-none-any.whl"))
1228
1229    result = script.pip(
1230        "install",
1231        "--no-cache-dir", "--no-index",
1232        "--find-links", script.scratch_path,
1233        "--verbose",
1234        "A",
1235        allow_stderr_warning=True,
1236    )
1237
1238    assert " different version in metadata: '2'" in result.stderr, str(result)
1239    assert_installed(script, a="1")
1240
1241
1242@pytest.mark.parametrize(
1243    "upgrade",
1244    [True, False],
1245    ids=["upgrade", "no-upgrade"],
1246)
1247def test_new_resolver_lazy_fetch_candidates(script, upgrade):
1248    create_basic_wheel_for_package(script, "myuberpkg", "1")
1249    create_basic_wheel_for_package(script, "myuberpkg", "2")
1250    create_basic_wheel_for_package(script, "myuberpkg", "3")
1251
1252    # Install an old version first.
1253    script.pip(
1254        "install",
1255        "--no-cache-dir", "--no-index",
1256        "--find-links", script.scratch_path,
1257        "myuberpkg==1",
1258    )
1259
1260    # Now install the same package again, maybe with the upgrade flag.
1261    if upgrade:
1262        pip_upgrade_args = ["--upgrade"]
1263    else:
1264        pip_upgrade_args = []
1265    result = script.pip(
1266        "install",
1267        "--no-cache-dir", "--no-index",
1268        "--find-links", script.scratch_path,
1269        "myuberpkg",
1270        *pip_upgrade_args  # Trailing comma fails on Python 2.
1271    )
1272
1273    # pip should install the version preferred by the strategy...
1274    if upgrade:
1275        assert_installed(script, myuberpkg="3")
1276    else:
1277        assert_installed(script, myuberpkg="1")
1278
1279    # But should reach there in the best route possible, without trying
1280    # candidates it does not need to.
1281    assert "myuberpkg-2" not in result.stdout, str(result)
1282